Flutter, le framework d’application multiplateforme de Google, a non seulement gagné en popularité dans le domaine du développement d’applications mobile, mais s’est également étendu sans effort au web, à Linux, à macOS et à Windows. Pour ne rien gâcher, Flutter est ultra-rapide et c’est un réel plaisir d’y travailler.
En ce qui concerne l’internationalisation (i18n) des applications Flutter, l’équipe a conçu une solution intégrée robuste. Dans ce tutoriel, nous allons voir comment configurer et paramétrer les bibliothèques d’internationalisation Flutter, les utiliser pour charger et afficher des traductions et travailler sur le format des dates/heures, parmi d’autres fonctionnalités de localisation.
🤿 Pour approfondir » Le package de localisation natif de Flutter est basé sur le premier package Dart intl :

Phrase Strings
Take your web or mobile app global without any hassle
Adapt your software, website, or video game for global audiences with the leanest and most realiable software localization platform.
L’application de démonstration
Pour que ce soit plus concret et plus amusant, nous allons concevoir une petite application de démonstration et la localiser : Héros de l’informatique présente une sélection de figures notables dans l’histoire relativement courte de l’informatique.

Versions utilisées
Dans cet article, nous utilisons les versions suivantes (langues, framework et packages) :
- Dart 3.1.1
- Flutter 3.13.3
- DevTools 2.25.0
- flutter_localizations (la version semble liée à Flutter) — fournit des localisations pour des widgets courants, comme Material ou Cupertino.
- intl 0.18.0 — la colonne vertébrale du système de localisation ; nous permet de créer et d’utiliser nos propres localisations ; utilisé pour le format des dates et des nombres.
Maintenant, regardons le code de notre application de démarrage, qui est assez simple.
import 'package:flutter/material.dart';
import 'screens/hero_list.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Heroes of Computer Science',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HeroList(title: 'Heroes of Computer Science'),
);
}
}Langage du code : Dart (dart)
Notre widget racine est un MaterialApp, avec un HeroList à sa route home.
import 'package:flutter/material.dart';
import 'package:flutter_i18n_2021/screens/settings.dart';
import 'package:flutter_i18n_2021/widgets/hero_card.dart';
class HeroList extends StatelessWidget {
final String title;
HeroList({this.title = ''});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
tooltip: 'Open settings',
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Settings()),
);
},
)
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text('6 Heroes'),
),
Expanded(
child: ListView(
children: <Widget>[
HeroCard(
name: 'Grace Hopper',
born: '9 December 1906',
bio: 'Devised theory of machine...',
),
HeroCard(
name: 'Alan Turing',
born: '23 June 1912',
bio: 'Father of theoretical computer...',
),
// ...
],
),
),
],
),
),
);
}
}Langage du code : Dart (dart)
HeroList contient principalement une ListView des HeroCard paramétrés.
import 'package:flutter/material.dart';
class HeroCard extends StatelessWidget {
final String name;
final String born;
final String bio;
final String imagePath;
final String placeholderImagePath = 'assets/images/placeholder.jpg';
const HeroCard({
Key key,
this.name = '',
this.born = '',
this.bio = '',
this.imagePath,
}) : super(key: key);
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
return Card(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(2),
child: Image.asset(
imagePath ?? placeholderImagePath,
width: 100,
height: 100,
),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
name,
style: theme.textTheme.headline6,
),
),
Padding(
padding: const EdgeInsets.only(top: 2, bottom: 4),
child: Text(
born.isEmpty ? '' : 'Born $born',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w300,
),
),
),
Text(
bio,
style: TextStyle(fontSize: 14),
),
],
),
),
],
),
),
);
}
}
Langage du code : JavaScript (javascript)
HeroCard affiche l’image correspondante et les paramètres de chaîne dans un widget Material Card et présente l’ensemble de manière esthétique. Nous pouvons maintenant passer à la localisation.
🔗 Ressources » Vous pouvez obtenir le code de l’application jusqu’à ce point à partir de la branche ‘start’ de notre dépôt GitHub. La branche principale contient également l’application entièrement localisée.
Installation et configuration
Nous pouvons installer nos packages en ajoutant quelques lignes à pubspec.yaml.
version: 1.0.0+1
environment:
sdk: '>=3.1.1 <4.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
# Add the flutter_localizations package
flutter_localizations:
sdk: flutter
# Add the intl package
intl: ^0.18.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
generate: true # Add this line
uses-material-design: trueLangage du code : YAML (yaml)
Après avoir ajouté les lignes surlignées ci-dessus, nous pouvons exécuter flutter pub get à partir de la ligne de commande pour récupérer nos packages. La ligne generate: true est nécessaire pour la génération automatique de code que les packages de localisation nous fournissent. Nous approfondirons par la suite l’activité de génération de code. Pour l’instant, incluez la ligne. Cela peut vous faire gagner beaucoup de temps.
Configuration de la localisation
Avec nos packages installés, ajoutons un fichier l10n.yaml à la racine de notre projet. Ce fichier configure l’endroit où se trouveront nos fichiers de traduction et les noms des fichiers dart générés automatiquement.
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dartLangage du code : YAML (yaml)
🔗 Ressources » Le Guide utilisateur officiel de l’internationalisation couvre de nombreuses autres options qui peuvent être ajoutées dans l10n.yaml pour contrôler le générateur de code d’internationalisation de Flutter.
Ajout des fichiers de traduction
La localisation du contenu Flutter utilise par défaut des fichiers ARB (Application Resource Bundle) pour stocker les traductions. Ce sont des fichiers simples écrits en syntaxe JSON. Au minimum, nous avons besoin d’un fichier modèle qui correspond à nos paramètres régionaux par défaut (l’anglais dans notre cas). Nous avons spécifié que notre fichier modèle sera lib/l10n/app_en.arb dans notre configuration ci-dessus. Nous allons maintenant créer ce répertoire de stockage et y ajouter notre fichier de traductions modèle.
{
"appTitle": "Heroes of Computer Science"
}Langage du code : JSON / JSON avec commentaires (json)
Bien sûr, toutes ces opérations n’auraient pas beaucoup de sens si nous ne pouvions pas fournir des traductions pour d’autres paramètres régionaux. Dans notre exemple, nous ajouterons un fichier de traductions en arabe. N’hésitez pas à ajouter la langue de votre choix. Nous aborderons les mises en page de droite à gauche (RTL) un peu plus tard, donc si cela vous intéresse, optez pour l’arabe ou une autre langue se lisant de droite à gauche.
{
"appTitle": "أبطال علوم الكمبيوتر"
}Langage du code : JSON / JSON avec commentaires (json)
Nous pouvons ajouter autant de traductions de paramètres régionaux que nous le souhaitons. Nous devons simplement nous assurer que nos fichiers respectent la convention de nommage configurée : lib/l10n/app_<paramètres régionaux>.arb
Configuration de notre application
Commençons à informer notre application de notre vif intérêt pour l’internationalisation. Nous devons configurer notre fichier main.dart pour utiliser les packages de localisation Flutter.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'screens/hero_list.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Heroes of Computer Science',
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
// 'en' is the language code. We could optionally provide a
// country code as the second param, e.g.
// Locale('en', 'US'). If we do that, we may want to
// provide an additional app_en_US.arb file for
// region-specific translations.
const Locale('en', ''),
const Locale('ar', ''),
],
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HeroList(title: 'Heroes of Computer Science'),
);
}
}
Langage du code : JavaScript (javascript)
Après avoir importé flutter_localizations.dart, nous ajoutons les propriétés localizationsDelegates et supportedLocales au constructeur MaterialApp. Les localizationsDelegates fournissent des localisations à notre application. Celles incluses ci-dessus fournissent des localisations pour les widgets Flutter, Material et Cupertino, qui ont déjà été localisés par l’équipe Flutter.
Par exemple, supposons que nous avions un MaterialApp et que nous appelions la fonction showDatePicker() quelque part dans ce dernier. Supposons également que la langue de notre système d’exploitation soit l’arabe ; nous verrions alors quelque chose comme ce qui suit.

Notez que nous n’avons pas eu à traduire quoi que ce soit nous-mêmes. Le widget de sélection de date a déjà été localisé par l’équipe Flutter. Il nous faut juste connecter les bons délégués dans le constructeur de l’application, comme nous l’avons fait ci-dessus. Un grand merci à l’équipe Flutter : quel gain de temps !
🗒️ Remarque » Au moment de la rédaction, flutter_localizations prend en charge 78 langues.
🔗 Ressources » La documentation Flutter officielle détaille comment les différentes parties, comme les délégués et la classe Localizations, fonctionnent ensemble pour l’internationalisation/la localisation.
La propriété supportedLocales que nous avons fournie au constructeur MaterialApp contient la liste des langues que notre application prend en charge. Flutter ne reconstruira que les widgets en réponse à un changement de paramètres régionaux si le paramètre régional est dans la liste supportedLocales. Nous reviendrons à supportedLocales dans un instant lorsque nous discuterons de la résolution des paramètres régionaux. Pour l’instant, occupons-nous de la génération du code.
Génération de code automatique
Pour utiliser les traductions dans les fichiers ARB dans notre application Flutter, nous devons générer des fichiers dart que nous importons chaque fois que nous avons besoin des traductions. Pour générer ces fichiers, assurez-vous simplement d’avoir suivi les étapes d’installation et de configuration jusqu’à ce point et d’exécuter l’application. Il suffit d’exécuter l’application. Le code sera généré automatiquement et, si tout s’est bien passé, vous devriez voir les fichiers suivants dans votre répertoire de projet :
.dart_tool/flutter_gen/gen_l10n/app_localizations.dart.dart_tool/flutter_gen/gen_l10n/app_localizations_en.dart.dart_tool/flutter_gen/gen_l10n/app_localizations_ar.dart
🗒️ Remarque » Si ces fichiers n’ont pas été générés, assurez-vous que votre application Flutter ne comporte aucune erreur de compilation et vérifiez votre console de débogage lorsque vous exécutez l’application.
Utiliser notre AppLocalizations
Utilisons les fichiers de code nouvellement générés pour localiser le titre de notre application.
import 'package:flutter/material.dart';
import 'package:flutter_i18n_2021/screens/settings.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'screens/hero_list.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateTitle: (context) {
return AppLocalizations.of(context).appTitle;
},
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),
const Locale('ar', ''),
],
theme: ThemeData(
primarySwatch: Colors.blue,
),
// remove home: HeroList(...)
initialRoute: '/',
routes: {
'/': (context) => HeroList(title: AppLocalizations.of(context).appTitle),
'/settings': (context) => Settings(),
},
);
}
}
Langage du code : Dart (dart)
Nous importons app_localizations.dart et ajoutons le AppLocalizations.delegate généré automatiquement à notre liste de délégués. Cela nous fournit le widget AppLocalizations, que nous utilisons pour traduire le titre de l’application et le titre de HeroList. La propriété appTitle générée automatiquement contiendra la traduction correspondant au paramètre régional actif, extrait de notre fichier app_<locale>.arb.
✋ Avertissement » En raison de l’ordre de chargement, nos traductions ne seront pas prêtes lorsque nous construisons notre MaterialApp. Nous utilisons donc les propriétés onGenerateTitle et routes, et leurs fonctions de constructeur (context) {} pour nous assurer que nos traductions sont prêtes lorsque nous définissons nos chaînes de titre.
Maintenant, si nous définissons la langue de notre système d’exploitation sur l’arabe et que nous exécutons notre application, voilà ce qu’on obtient.

Le titre est maintenant en arabe. De plus, remarquez comment Flutter a automatiquement organisé de nombreux widgets dans une direction de droite à gauche pour nous. L’arabe étant une langue qui se lit de droite à gauche, cela nous fait gagner beaucoup de temps ! Il va falloir corriger l’espacement à gauche de l’image dans les HeroCard, nous le ferons un peu plus tard lorsque nous aborderons la question du sens de lecture.
🤿 Pour approfondir » Le lecteur avisé a peut-être remarqué que AppLocalizations.of(context) ressemble beaucoup à l’appel d’un InheritedWidget. C’est parce que le fonctionnement des objets de localisation est similaire aux InheritedWidget.
Voilà, la configuration est terminée. Nous disposons désormais des bases nécessaires à la localisation de notre application. Vous vous demandez peut-être à ce stade comment Flutter décide du paramètre régional à utiliser. Voyons ça.
Résolution des paramètres régionaux
Les paramètres régionaux que nous avons fournis à MaterialApp(supportedLocales: [...]) sont les seuls que Flutter utilisera pour déterminer le paramètre régional actif lorsque l’application s’exécute. Pour ce faire, Flutter utilise trois propriétés d’un paramètre régional :
- Le code de langue, par exemple
'en'pour l’anglais - Le code de pays (facultatif) : par exemple, la partie
USdansen_US - Le code de script (facultatif) — l’ensemble de lettres utilisé, par exemple le chinois traditionnel (
Hant) ou le chinois simplifié (Hans)
Par défaut, Flutter lira les paramètres régionaux système préférés de l’utilisateur et :
- Essayez de faire correspondre
languageCode,scriptCodeetcountryCodeavec l’un de ceux danssupportedLocales. Sinon, - Essayez de faire correspondre
languageCodeandscriptCodeavec l’un de ceux danssupportedLocales. Sinon, - Essayez de faire correspondre
languageCodeandcountryCodeavec l’un de ceux danssupportedLocales. Sinon, - Essayez de faire correspondre
languageCodeavec l’un de ceux danssupportedLocales. Sinon, - Essayez de faire correspondre
countryCodeavec l’un de ceux danssupportedLocales, uniquement aucun paramètre régional préféré ne correspond pas. Sinon, - À défaut, utilisez le premier élément de
supportedLocalesen solution de secours.
Dans le cas de notre application, si la langue iOS de l’utilisateur est définie sur ar_SA, il verra nos localisations ar (cf. 4. ci-dessus). Si la langue iOS de l’utilisateur est définie sur fr (français), il verra nos localisations en (cf. 6 ci-dessus). Sur Android, un utilisateur peut avoir une liste de paramètres régionaux préférés, et pas uniquement un. Cette situation est couverte par Flutter dans l’algorithme de résolution ci-dessus.
🔗 Ressources » L’algorithme ci-dessus est une reformulation de la documentation officielle de la propriété supportedLocales.
✋ Avertissement : Si votre application prend en charge un paramètre linguistique avec un code pays, comme fr_CA (français canadien), vous devez fournir une solution de secours sans le code pays, comme fr.
Mise à jour du projet iOS
La documentation officielle Flutter mentionne la nécessité de mettre à jour Info.plist directement dans le paquet de l’application iOS, en ajoutant nos paramètres régionaux pris en charge. Si Info.plist n’est pas mis à jour, notre application iOS pourrait ne pas fonctionner comme prévu. Pour effectuer la mise à jour, il suffit d’ouvrir ios/Runner/Info.plist dans n’importe quel éditeur de texte et de s’assurer que les entrées suivantes y figurent.
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>ar</string>
</array>Langage du code : Texte brut (plaintext)
Obtenir le paramètre régional actif
Nous devons parfois connaître le paramètre régional d’exécution dans notre code. Nous pouvons le faire avec le morceau de code suivant.
Locale activeLocale = Localizations.localeOf(context);
// Assuming our active locale is fr_CA...
debugPrint(activeLocale.languageCode);
// => fr
debugPrint(activeLocale.countryCode);
// => CALangage du code : Dart (dart)
✋ Avertissement » Remarquez que nous utilisons Localizations, un widget intégré à Flutter, et non AppLocalizations généré automatiquement.
Messages de base de traduction
Nous avons déjà couvert les messages de traduction de base lorsque nous avons ajouté notre message appTitle. Cependant, passons rapidement en revue le flux de travaux pour ajouter des messages. Nous allons traduire l’info-bulle du bouton d’icône de la barre d’application de notre HeroList. Autant en profiter un peu puisque nous ne construisons pas vraiment un écran de paramètres 😅.
// ...
class HeroList extends StatelessWidget {
final String title;
HeroList({this.title = ''});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
tooltip: 'Open settings',
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Settings()),
);
},
)
],
),
body: ...
);
}
Langage du code : Dart (dart)
Nous allons nous occuper de localiser cette info-bulle. Tout d’abord, nous allons ajouter les entrées pertinentes à nos fichiers ARB.
// English
{
"appTitle": "Heroes of Computer Science",
"openSettings": "Open Settings"
}
// Arabic
{
"appTitle": "أبطال علوم الكمبيوتر",
"openSettings": "إفتح الإعدادات"
}Langage du code : JSON / JSON avec commentaires (json)
Nous allons recharger notre application pour régénérer nos fichiers de code. Cette étape est vraiment importante et l’oublier peut entraîner une frustration inutile. Notez qu’il s’agit d’un redémarrage complet de l’application (

), et non d’un rechargement à chaud.
Maintenant, nous pouvons mettre à jour notre code pour utiliser notre nouveau message localisé.
// ...
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class HeroList extends StatelessWidget {
final String title;
HeroList({this.title = ''});
@override
Widget build(BuildContext context) {
var t = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
tooltip: t.openSettings,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Settings()),
);
},
)
],
),
body: ...
);
}
}
Langage du code : Dart (dart)
Tout étant en ordre pour ce code, lorsque nous rechargerons notre application, nous devrions voir la valeur localisée de notre info-bulle dans l’inspecteur de widget.

✋ Avertissement » Il est possible que votre IDE vous indique une erreur après l’ajout de nouveaux messages de traduction. Si vous avez rechargé votre application, l’erreur peut être incorrecte (il se peut que tout fonctionne correctement). Si vous obtenez un message indiquant qu’il y a des erreurs de compilation, vous pouvez essayer de lancer l’application quand même. Tant que l’application se compile et s’exécute et que vous voyez vos nouvelles traductions, tout fonctionne probablement correctement. Pour faire disparaître l’erreur dans l’IDE, fermez complètement votre application, puis redémarrez-la.
Interpolation dans les messages
Nous sommes bien partis pour traduire notre application. Mais qu’en est-il de l’interpolation des valeurs dynamiques d’exécution dans nos messages de traduction ? Par exemple, la biographie de Steve Wozniak contient les noms de produits Apple I et Apple II.
// ...
class HeroList extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return Scaffold(
// ...
body: HeroCard(
name: 'Steve Wozniak',
born: '11 August 1950',
bio: 'Designed & developed the Apple I & '
'Apple II microcomputers.',
imagePath: 'assets/images/steve_wozniak.jpg',
),
// ...
);
}
}Langage du code : Dart (dart)
Lorsque nous localisons ce message, nous pourrions vouloir garder Apple I et Apple II en anglais, peu importe les paramètres régionaux actifs. Pour ce faire, nous pouvons utiliser un espace réservé dans nos fichiers de traduction.
// English
{
// ...
"wozniakBio": "Developed the {appleOne} & {appleTwo} microcomputers.",
"@wozniakBio": {
"placeholders": {
"appleOne": {},
"appleTwo": {}
}
},
// ...
}
// Arabic
{
// ...
"wozniakBio": "طور جهازي كمبيوتر {appleOne} و {appleTwo}",
// ...
}
Langage du code : JSON / JSON avec commentaires (json)
Nous utilisons la syntaxe {placeholderName} pour définir les espaces réservés pour nos valeurs dynamiques et nous pouvons avoir autant d’espaces réservés que nous le souhaitons dans un message.
Vous avez probablement aussi remarqué la clé @wozniakBio dans nos traductions anglaises ci-dessus. Cette entrée est un complément au message wozniakBio dans le même fichier. Les entrées complémentaires sont facultatives pour les messages de base, mais requises pour les messages avec des espaces réservés. En fait, nous utilisons des entrées complémentaires pour définir les espaces réservés des messages (entre autres choses).
🗒️ Remarque » L’entrée complémentaire pour un message avec la clé foo doit avoir une clé de @foo. Nous n’avons besoin d’entrées complémentaires que dans notre fichier de traduction par défaut/modèle (l’anglais dans notre cas).
"@wozniakBio": { "placeholders": { "appleOne": {}, "appleTwo": {} } }Langage du code : JavaScript (javascript)
✋ Avertissement » Les noms d’espaces réservés doivent être des noms de paramètres de méthode dart valides.
Nous pourrions utiliser l’objet placeholders pour spécifier le type de chaque valeur, et même fournir des exemples comme documentation si nous le souhaitons. Nous pouvons également laisser la définition comme un vide {}.
"@wozniakBio": {
"placeholders": {
"appleOne": {
// Explicit type
"type": "String",
// A little doc
"example": "Apple I"
},
// It's perfectly ok to just specifiy the name
"appleTwo": {}
}
}Langage du code : PHP (php)
Le type est utilisé dans la méthode que Flutter générera sur AppLocalizations pour notre message wozniakBio.
// ...
abstract class AppLocalizations {
// ...
// This method is implemented in app_localizations_en.dart
// and app_localizations_ar.dart.
// Explicit type for appleOne parameter. Implicit appleTwo
// parameter.
String wozniakBio(String appleOne, Object appleTwo);
// ...
}
Langage du code : JavaScript (javascript)
🗒️ Remarque » Il est parfaitement acceptable dans la plupart des cas d’utiliser {} vide pour les définitions des espaces réservés. Une définition vide fera en sorte que le paramètre soit de type Object. Flutter utilisera simplement la valeur theParameter.toString() du paramètre concerné, donc un paramètre Object fonctionnera parfaitement. Un espace réservé Object implicite garde également nos messages flexibles pour prendre n’importe quel type, puisque tous les types dart dérivent de Object et ont un toString().
Après avoir relancé l’application pour mettre à jour AppLocalizations, nous pouvons localiser le message de la biographie de Woz avec la nouvelle méthode.
// ...
class HeroList extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
var t = AppLocalizations.of(context);
return Scaffold(
// ...
body: HeroCard(
name: 'Steve Wozniak',
born: '11 August 1950',
bio: t.wozniakBio('Apple I', 'Apple II'),
imagePath: 'assets/images/steve_wozniak.jpg',
),
// ...
);
}
}
Langage du code : Dart (dart)
Maintenant, nous avons l’assurance de ne jamais être poursuivis par des entreprises pour avoir mal représenté leurs produits dans n’importe quelle langue.

Pluriels
Nous devons souvent gérer des pluriels dynamiques dans nos opérations de localisation. « Vous avez reçu un message » ou « Vous avez reçu 3 messages« , par exemple.
Il est important de noter que les langues gèrent les pluriels différemment. Par exemple, l’anglais a deux formes plurielles : one et other (other == zéro et >1). L’arabe en a six. Cela peut poser quelques difficultés lors de la localisation en utilisant des bibliothèques qui ne prennent pas en charge les règles de pluriels complexes. Heureusement, la solution d’internationalisation officielle de Flutter gère les pluriels complexes, ce qui correspond à nos besoins. Utilisons-la pour localiser le compteur de héros dans notre application.

// ...
class HeroList extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return Scaffold(
// ...
body: Padding(
// ...
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
// Localized string should replace '6 Heroes'
child: Text('6 Heroes'),
),
// ...
],
),
),
);
}
}
// ...
Langage du code : Dart (dart)
Tout d’abord, ajoutons le message à notre fichier modèle ARB en anglais.
{
// ...
"heroCount": "{count,plural, =0{No heroes yet} =1{1 hero} other{{count} heroes}}",
"@heroCount": {
"placeholders": {
"count": {}
}
},
// ...
}
Langage du code : JSON / JSON avec commentaires (json)
Nous spécifions un espace réservé count dans notre message et nous l’utilisons avec la syntaxe spéciale {count,plural,...} pour définir les différentes formes plurielles.
✋ Avertissement » : Le paramètre count sera toujours de type int. Si vous spécifiez un autre type pour count, Flutter l’ignorera et utilisera int de toute façon.
🗒️ Remarque » Vous pouvez ajouter d’autres espaces réservés que count pour un message pluriel ; ils sont spécifiés comme d’habitude (voir la section Interpolation ci-dessus).
Flutter prend en charge les formes plurielles suivantes.
- zero ➞
=0{No heroes} - one ➞
=1{One hero} - two ➞
=2(Two heroes} - few ➞
few{The {count} heroes} - many ➞
many{{count} heroes} - other ➞
other{{count} heroes}
few, many et other ont des significations différentes selon la langue active. La seule forme requise dans n’importe quelle langue est la forme other.
🗒️ Remarque » Nous n’avions pas besoin d’utiliser la forme zero =0 dans notre message en anglais ci-dessus. En cas d’omission, Flutter aurait utilisé notre forme other à la place.
Ajoutons maintenant notre message en arabe. Comme nous l’avons mentionné plus tôt, l’arabe a six formes plurielles.
{
// ...
"heroCount": "{count,plural, =0{لا توجد أبطال بعد} =1{بطل واحد} =2{بطلان} few{{count} أبطال} many{{count} بطل} other{{count} بطل}}",
// ...
}Langage du code : JSON / JSON avec commentaires (json)
Maintenant, connectons le tout et utilisons notre nouveau message dans notre widget HeroList.
// ...
class HeroList extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
var t = AppLocalizations.of(context);
return Scaffold(
// ...
body: Padding(
// ...
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(t.heroCount(6)),
),
// ...
],
),
),
);
}
}
// ...
Langage du code : Dart (dart)
Bien sûr, dans une application de production, le paramètre count passé à t.heroCount() serait dynamique. Flutter choisit la bonne forme plurielle de notre message en fonction des paramètres régionaux actifs.


Format des nombres
Nous pouvons formater les nombres dans nos messages localisés en utilisant notre ami l’objet placeholders dans les entrées compagnon de notre fichier modèle ARB (anglais dans notre cas). Notre application de démonstration ne permet pas d’aborder la question du formatage des nombres, donc nous allons simplement prétendre pour la démonstration que nous avons une application de commerce électronique.
// app_en.arb in example app that has a shopping cart
{
"itemTotal": "Your total is: {value}",
"@itemTotal": {
"placeholders": {
"value": {
"type": "double",
"format": "currency"
}
}
}
}
Langage du code : JSON / JSON avec commentaires (json)
Nous avons spécifié un type explicite et un format pour contrôler la façon dont le nombre sera affiché. Comme d’habitude, nous pouvons traduire notre message dans nos autres fichiers de paramètres régionaux.
// app_ar.arb in example app that has a shopping cart
{ "itemTotal": "إجمالي: {value}" }Langage du code : JSON / JSON avec commentaires (json)
Après avoir rechargé notre application, nous pouvons utiliser notre message comme d’habitude.
// In a Widget
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
// In some widget builder with a context
var t = AppLocalizations.of(context);
var message = t.itemTotal(56.12);
// => "Your total is USD56.12" when current locale is English
// => "إجمالي: EGP56.12" when current locale is Arabic
Langage du code : Dart (dart)
✋ Avertissement » Vous ne pouvez pas remplacer les formats de nombres selon les paramètres linguistiques. Le format que vous spécifiez dans vos paramètres linguistiques modèles (l’anglais dans notre cas) sera utilisé pour les autres paramètres linguistiques, indépendamment de tout changement du format que vous spécifiez dans vos autres fichiers de paramètres linguistiques.
N’oubliez pas qu’en coulisses, Flutter utilise la bibliothèque Dart intl pour la plupart de son travail d’internationalisation. Le format de devise que nous avons utilisé ci-dessus est l’un des formats intégrés au formateur de nombres intl. Les autres formats incluent notamment des décimales et des pourcentages.
🔗 Ressources » Consultez le guide utilisateur officiel pour connaître tous les formats disponibles.
Cependant, nous n’avons pas besoin que Flutter internationalise les nombres. Nous pouvons utiliser intl directement pour avoir plus de contrôle sur notre format de nombre.
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
// In some Widget builder with a context
var currentLocale = AppLocalizations.of(context).localeName;
var compact = NumberFormat.compact(locale: currentLocale).format(6000000);
// => "6M" when current locale is US English
// => "٦ مليون" when current locale is Egyptian Arabic
var simpleCurrency = NumberFormat.simpleCurrency(locale: currentLocale).format(14.24);
// => "$14.24" when current locale is US English
// => "ج.م. ١٤٫٢٤" when current locale is Egyptian ArabicLangage du code : Dart (dart)
🔗 Ressources » Vous n’avez pas besoin d’utiliser les formats prédéfinis comme compact et simpleCurrency. Le constructeur d’internationalisation NumberFormat vous donne un contrôle précis sur vos formats de nombre. Pour en savoir plus, reportez-vous à la documentation officielle.
✋ Avertissement » La seule façon d’obtenir des chiffres arabes orientaux (١،٢،٣…) pour l’arabe est de définir le paramètre locale sur "ar_EG" (arabe égyptien). Ni "ar" ni aucune variante "ar_XX" autre que l’égyptien n’ont fonctionné pour moi.
✋ Avertissement » Les formats n’ont pas fonctionné avec la variable plurielle count pour moi. Il semble que Flutter remplace le format lorsqu’il traite les pluriels. Si vous parvenez à obtenir des formats dans vos pluriels, veuillez nous faire savoir comment vous avez fait dans les commentaires ci-dessous.
Format de date
Nos héros ont actuellement des dates de naissance codées en dur qui ne sont pas localisées, ce qui n’est pas idéal.

Rappelez-vous que nos héros sont traités via un widget HeroCard.
import 'package:flutter/material.dart';
class HeroCard extends StatelessWidget {
final String name;
final String born;
final String bio;
final String imagePath;
// ...
const HeroCard({
Key key,
this.name = '',
this.born = '',
this.bio = '',
this.imagePath,
}) : super(key: key);
@override
Widget build(BuildContext context) {
// ...
return Card(
child: Padding(
// ...
Padding(
padding: const EdgeInsets.only(top: 2, bottom: 4),
child: Text(
born.isEmpty ? '' : 'Born $born',
// ...
),
),
// ...
),
);
}
}Langage du code : Dart (dart)
Pour formater la date de naissance pour les différents paramètres régionaux pris en charge par notre application, nous ajoutons d’abord quelques nouveaux messages localisés avec des valeurs de date interpolées.
// English
{
// ...
"heroBorn": "Born {date}",
"@heroBorn": {
"placeholders": {
"date": {
"type": "DateTime",
"format": "yMMMd"
}
}
},
// ...
}
// Arabic
{
// ...
"heroBorn": "تاريخ الميلاد {date}",
// ...
}Langage du code : JSON / JSON avec commentaires (json)
Lorsque nous définissons notre espace réservé date dans notre fichier de localisation modèle, nous devons lui attribuer le type DateTime. Nous pouvons ensuite utiliser format pour spécifier la manière dont nous voulons afficher la date. Le format yMMMd que nous avons défini ci-dessus correspond à « année, mois abrégé, jour », ce qui en anglais américain donnerait quelque chose comme « Dec 9, 1906 ».
🔗 Resources » En coulisses, Flutter utilise simplement la classe d’internationalisation DateFormat, générant du code comme DateFormat.yMMMd(localeName).format(date). Le constructeur nommé yMMMd est un raccourci pratique appelé « squelette » et il en existe plusieurs que vous pouvez utiliser. Pour en savoir plus, consultez la documentation officielle DateFormat.
🗒️ Remarque » Nous n’avons pas eu à appeler notre variable d’espace réservé date. Nous aurions pu lui donner n’importe quel nom, tant que c’était un nom de paramètre de fonction Dart valide.
Connectons cela dans notre widget pour afficher nos nouveaux messages.
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class HeroCard extends StatelessWidget {
final String name;
final String born;
final String bio;
final String imagePath;
// ...
final DateTime bornDateTime;
HeroCard({
Key key,
this.name = '',
this.born = '',
this.bio = '',
this.imagePath,
}) : bornDateTime = DateFormat('d MMMM yyyy').parse(born),
super(key: key);
@override
Widget build(BuildContext context) {
// ...
var t = AppLocalizations.of(context);
return Card(
child: Padding(
// ...
Padding(
padding: const EdgeInsets.only(top: 2, bottom: 4),
child: Text(
born.isEmpty ? '' : t.heroBorn(bornDateTime),
// ...
),
),
// ...
),
);
}
}Langage du code : Dart (dart)
La date de naissance des héros se présentant sous la forme String, nous devons d’abord la parser en DateTime. Pour ce faire, nous utilisons la classe d’internationalisation DateFormat dans notre constructeur de widget.
Dans la méthode build, nous passons simplement le DateTime parsé à notre message localisé t.heroBorn(). Cela nous donne des dates correctement localisées.


Si nous ne voulons pas utiliser l’un des squelettes prédéfinis, comment personnaliser complètement nos formats de date ? Comme pour le formatage des nombres (voir ci-dessus), nous utiliserions directement la classe intl.DateFormat. Supposons que nous voulions afficher les dates de naissance des héros au format 1912-06-23. Nous procéderions comme suit.
import 'package:intl/intl.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
// In widget builder with context
var t = AppLocalizations.of(context);
var bornDateTime = DateTime(1912, 6, 23);
var formattedBorn = DateFormat('yyyy-MM-dd', t.localeName).format(bornDateTime);
var message = t.heroBorn(formattedBorn);
// => "1912-06-23" in US English
// => "١٩١٢-٠٦-٢٣" in Egyptian Arabic
Langage du code : JavaScript (javascript)
Nos messages localisés heroBorn dans nos fichiers ARB prendraient alors simplement des paramètres réguliers Object ou String, puisque nous avons déjà fait le formatage pour ces derniers.
Sens de lecture : de gauche à droite et de droite à gauche
Alors que l’anglais est une langue qui se lit de gauche à droite (LTR), l’arabe se lit dans l’autre sens, de droite à gauche (RTL). Cela pose un problème lorsque notre application est utilisée sur un appareil avec l’arabe comme langue système.

L’image et le texte dans chaque carte sont alignés car nous utilisons EdgeInsets.only(right) pour définir l’espacement autour de notre image.
import 'package:intl/intl.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
// In widget builder with context
var t = AppLocalizations.of(context);
var bornDateTime = DateTime(1912, 6, 23);
var formattedBorn = DateFormat('yyyy-MM-dd', t.localeName).format(bornDateTime);
var message = t.heroBorn(formattedBorn);
// => "1912-06-23" in US English
// => "١٩١٢-٠٦-٢٣" in Egyptian Arabic
Langage du code : Dart (dart)
Cela fonctionne dans les langues s’écrivant de gauche à droite (LTR), où nous voulons un espace à droite entre l’image et le texte. En revanche, dans les langues s’écrivant de droite à gauche (RTL), nous voulons un espace à gauche.
Il suffit d’utiliser EdgeInsetsDirectional au lieu de EdgeInsets.
//...
class HeroCard extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
// ...
return Card(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsetsDirectional.only(end: 8.0),
child: ClipRRect(
// Portrait image...
),
),
Expanded(
// Text widgets...
),
],
),
),
);
}
}Langage du code : PHP (php)
Remarquez que nous utilisons end au lieu de right pour définir l’espacement entre l’image et le texte. EdgeInsetsDirectional est l’un des quelques widgets de mise en page Flutter qui gèrent le sens de lecture des paramètres régionaux. Ces widgets utilisent les paramètres start et end, au lieu de left et right. Ces widgets directionnels font automatiquement ce qui convient pour les paramètres régionaux actifs :
start==leftpour les langues se lisant de gauche à droite (LTR)start==rightpour les langues se lisant de droite à gauche (RTL)end==rightpour les langues se lisant de gauche à droite (LTR)end==leftpour les langues se lisant de droite à gauche (RTL)
Grâce à ce petit ajustement du code, notre problème de mise en page est résolu.

🔗 Ressources » Au moment de la rédaction, la documentation officielle de Flutter inclut les widgets directionnels suivants :
- EdgeInsetsDirectional
- AlignmentDirectional
- BorderDirectional
- BorderRadiusDirectional
- PositionedDirectional
- AnimatedPositionedDirectional
Voilà, notre application finale est globalisée.

🔗 Ressources » Obtenez le code complet de notre application de démonstration depuis notre dépôt GitHub.
Ajouter des ressources localisées
La localisation des images dans Flutter implique d’utiliser différents ensembles d’images pour les différents paramètres régionaux ou les différentes langues dans votre application. Cela se fait généralement pour afficher des images avec du texte ou un contenu correspondant à la langue préférée de l’utilisateur.
Ajoutons un drapeau, qui s’affiche différemment selon la région de l’utilisateur.

Nous commencerons par ajouter les images, une pour chaque paramètre régional que nous prévoyons de prendre en charge. Nous allons organiser ces images dans une structure de dossiers basée sur les paramètres régionaux, par exemple :
└── assets/
└── images/
├── eg/
│ └── flag.jpg
├── us/
│ └── flag.jpg
└── flag.jpgLangage du code : Texte brut (plaintext)
Pour ce tutoriel, nous allons ajouter les images des drapeaux de l’Égypte et des États-Unis. Le fichier directement dans le dossier images (sans paramètres régionaux) est une image de secours pour les autres régions.
Ensuite, enregistrez les fichiers dans le fichier pubspec.yml.
# pubspec.yml
# ...
flutter:
# ...
assets:
# ...
- assets/images/in/flag.jpg
- assets/images/us/flag.jpg
- assets/images/flag.jpg
# ...Langage du code : YAML (yaml)
Maintenant, nous pouvons charger les images dans notre fichier hero_list.dart :
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../widgets/hero_card.dart';
class HeroList extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ...
String getImagePath(String imageName) {
String basePath = 'assets/images/';
// When country code isn't supported, we
// display fallback image.
if (locale.countryCode?.isEmpty == true) {
return basePath + 'flag.jpg';
}
String localePath = '${locale.countryCode!.toLowerCase()}/';
return basePath + localePath + imageName;
}
return Scaffold(
// ...
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// ...
Padding(
padding: const EdgeInsets.only(bottom: 2.0),
child: Image.asset(
getImagePath('flag.jpg'),
width: 40,
height: 40,
),
),
],
),
),
);
}
}Langage du code : Dart (dart)
Maintenant, dans le fichier main.dart, apportez les modifications suivantes
...
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
...
supportedLocales: [
const Locale('ar', ''),
const Locale('en', ''),
// Add the supported region codes
const Locale('ar', 'EG'),
const Locale('en', 'US'),
],
...
}
}Langage du code : Dart (dart)
Maintenant, l’application affiche la ressource appropriée pour les paramètres régionaux de l’utilisateur et montre une solution de secours en cas de non prise en charge de ses paramètres régionaux.

Changer la langue dans l’application
Parfois, un utilisateur voudra avoir son système d’exploitation dans une langue et une application spécifique, dans une autre. Pour prévoir cette éventualité, ajoutons un sélecteur de langue dans notre application. Cela fonctionne normalement pour toutes les plateformes prises en charge par Flutter.

Dans le fichier main.dart, nous allons apporter les modifications suivantes :
void main() {
runApp(const MyApp());
}
// We need to make MyApp Stateful because
// it needs to react when the Locale changes.
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
static void setLocale(BuildContext context, Locale newLocale) {
_MyAppState? state = context.findAncestorStateOfType<_MyAppState>();
state?.setLocale(newLocale);
}
}
class _MyAppState extends State<MyApp> {
Locale? _locale;
setLocale(Locale locale) {
setState(() {
_locale = locale;
});
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
// ...
locale: _locale,
initialRoute: '/',
routes: {
'/': (context) {
return HeroList(title: AppLocalizations.of(context)!.appTitle);
},
},
);
}
}Langage du code : Dart (dart)
Maintenant, dans le fichier hero_list.dart, créons le menu déroulant et appelons la méthode setLocale() que nous avons définie ci-dessus.
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../main.dart';
import '../widgets/hero_card.dart';
class HeroList extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
var t = AppLocalizations.of(context)!;
final Locale locale = Localizations.localeOf(context);
// Dropdown options
var items = [
'en',
'ar',
];
return Scaffold(
appBar: AppBar(
title: Text(title),
actions: <Widget>[
DropdownButton(
// Down Arrow Icon
icon: const Icon(Icons.settings, color: Colors.white,),
items: items.map((String items) {
return DropdownMenuItem(
value: items,
child: Text(items),
);
}).toList(),
onChanged: (String? newValue) {
MyApp.setLocale(context, Locale(newValue));
},
),
],
),
// ...
);
}
}Langage du code : Dart (dart)
Désormais, l’utilisateur peut sélectionner la langue de l’application indépendamment de la langue du système.
🗒️ Remarque » Avant que des paramètres régionaux ne soient sélectionnés, ils seront définis sur les paramètres régionaux par défaut du système de l’utilisateur.

🗒️ Remarque » Avant que l’utilisateur ne sélectionne manuellement des paramètres régionaux dans l’application, les paramètres régionaux du système sont utilisés par défaut. Cependant, les paramètres linguistiques sélectionnés manuellement par l’utilisateur dans l’application ne seront pas conservés au redémarrage de l’application. Pour conserver les paramètres régionaux préférés de l’utilisateur dans l’application, vous devez les enregistrer et les récupérer en utilisant par exemple le plug-in Shared Preferences.
Le projet final est disponible sur GitHub.
Simplifier la localisation du contenu Flutter
Nous espérons que vous avez apprécié ce tutoriel consacré à la localisation du contenu Flutter et que vous avez appris quelques astuces pratiques. Si vous êtes prêt à passer au niveau supérieur en matière de localisation, Phrase Strings est votre solution de référence. Véritable assistant de localisation pour vos applications Flutter, Phrase Strings prend en charge les fichiers ARB et se révèle très convivial pour les développeurs grâce à son API et à son CLI faciles à utiliser. Il dispose également d’un éditeur de chaînes performant qui facilite considérablement le travail de traduction.
De plus, il se synchronise parfaitement avec GitHub, GitLab et Bitbucket, et propose même des traductions over-the-air pour les applications mobiles afin d’alléger la charge liée à la localisation, vous permettant de rester concentré sur ce qui compte le plus pour vous : le code. Découvrez toutes les fonctionnalités de Phrase pour les développeurs et comment elles peuvent vous aider à internationaliser plus rapidement vos applications.


