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. Au-delà de cela, 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 Flutter a conçu une solution intégrée robuste. Dans ce tutoriel, nous allons configurer et paramétrer les bibliothèques i18n de Flutter, les utiliser pour charger et afficher des traductions et travailler sur le format des dates/heures, parmi d’autres fonctionnalités de localisation.
🤿 Allez plus loin » 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.
Notre application de démonstration
Pour garder les choses ancrées et amusantes, nous allons construire 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
Nous utilisons les versions suivantes de langue, de framework et de package dans cet article :
- 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 les widgets 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 Starter, 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(
Titre : « Héros de l’Informatique »,
Thème : ThemeData(
Palette principale : Colors.blue,
),
Accueil HeroList (titre : 'Héros de l’Informatique'),
);
}
}Langage du code : Dart (dart)
Notre widget racine est un MaterialApp, avec un HeroList à sa route Accueil.
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(
Titre : Texte (titre),
actions: <Widget> [
IconButton(
icon: Icon(Icons.settings),
tooltip: Ouvrir les paramètres,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Settings()),
);
},
)
],
),
Corps : Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
Enfant : Text('6 Héros'),
),
Expanded(
Enfant : ListView(
children: <Widget>[
HeroCard(
Nom : 'Grace Hopper',
né : 9 décembre 1906,
bio: La théorie de la machine conçue...
),
HeroCard(
Nom : 'Alan Turing',
Date de naissance : « 23 juin 1912 »,
bio: « Père de l'informatique théorique … »
),
// ...
],
),
),
],
),
),
);
}
}Langage du code : Dart (dart)
HeroList contient principalement une ListView de HeroCard paramétrées.
import 'package:flutter/material.dart';
class HeroCard extends StatelessWidget {
final String name;
final String born;
final String bio;
final chaîne cheminImage;
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(
Enfant : Padding(
padding: const EdgeInsets.all(4.0),
Enfant : Row(
crossAxisAlignment : CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 8.0),
Enfant : ClipRRect(
borderRadius: BorderRadius.circular(2),
Enfant : Image.asset(
imagePath ?? placeholderImagePath,
largeur: 100 ,
hauteur: 100,
),
),
),
Élargi (
Enfant : Colonne (
Alignement sur l’axe transversal : CrossAxisAlignment.start,
children : <Widget>[
Padding(
padding: const EdgeInsets.only(top: 4,
Enfant : Texte(
Nom,
style: theme.textTheme.headline6,
),
),
Padding(
padding: const EdgeInsets.only(top: 2, bottom: 4), 2, bas : 4,
Enfant : Texte(
born.isEmpty ? '' : 'Né $born',
Style : TextStyle(
Taille de police : 12 ,
Poids de police : FontWeight.w300,
),
),
),
Texte(
bio ,
Style : TextStyle(taille de police : 14),
),
],
),
),
],
),
),
);
}
}
HeroCard affiche l’image donnée et les paramètres d’image et de chaîne dans un joli widget Card Material et embellit le tout. D’accord, passons à la localisation de ce projet !
🔗 Ressource » 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
Environnement :
sdk: '>=3.1.1 <4.0.0'
dépendances :
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
# Ajouter le package flutter_localizations
flutter_localizations:
sdk : flutter
# Ajouter le package intl
intl: ^0.18.0
dev_dependencies:
flutter_test:
sdk : flutter
flutter:
generate: true # ajouter cette ligne
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 generate: true ligne est nécessaire pour la génération automatique de code que les packages de localisation nous fournissent. Nous allons bientôt approfondir 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, ajoutez un fichier l10n.yaml à la racine de notre projet. Ce fichier configure où se trouveront nos fichiers de traduction et les noms des fichiers Dart générés automatiquement.
arb-dir: lib/l10n
fichier ARB de modèle : app_en.arb
fichier de localisation de sortie : app_localizations.dartLangage du code : YAML (yaml)
🔗 Ressource » Le Guide officiel d’internationalisation de l’utilisateur couvre de nombreuses autres options qui peuvent être ajoutées dans l10n.yaml pour contrôler le générateur de code i18n de Flutter.
Ajout de fichiers de traduction
La localisation Flutter utilise par défaut des fichiers ARB (Application Resource Bundle) pour stocker ses 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 (anglais dans notre cas). Nous avons spécifié que notre fichier modèle sera lib/l10n/app_en.arb dans notre configuration ci-dessus. Alors créons ce répertoire de stockage et ajoutons-y notre fichier de traductions modèle.
{
"appTitle": "Les héros de l'informatique"
}Langage du code : JSON / JSON avec commentaires (json)
Bien sûr, toutes ces manigances n’auraient pas beaucoup de sens si nous ne pouvions pas fournir des traductions pour d’autres paramètres régionaux. Nous ajouterons un fichier de traductions en arabe ici. 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, vous voudrez peut-être vous en tenir à l’arabe ou à une autre langue RTL.
{
"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 à parler à notre application de notre vif intérêt pour l’i18n. 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(
Titre : « Les héros de l'informatique »,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
// 'en' est le code de langue . Nous pourrions éventuellement fournir un
// code de pays comme deuxième paramètre, par exemple.
// Locale('en', 'US'). Si nous faisons cela, nous pourrions vouloir
// fournir un fichier app_en_US.arb supplémentaire pour
// Des traductions spécifiques à la région.
const Locale('en', ''),
const Locale('ar', ''),
],
Thème : ThemeData(
Palette principale : Colors.blue,
),
home: HeroList(title: « Les héros de l’informatique »),
);
}
}
Langage du code : PHP (php)
Après avoir importé flutter_localizations.dart, nous ajoutons les propriétés localizationsDelegates et supportedLocales au constructeur MaterialApp. 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 le Flutter Team.
Par exemple, supposons que nous avions un MaterialApp et que nous appelions la fonction showDatePicker() quelque part dans celle-ci. Supposons également que la langue de notre système d’exploitation soit réglée sur 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 pour y parvenir. Le widget de sélection de date a déjà été localisé par le Flutter Team. Il suffit de connecter les bons délégués dans le constructeur de l’application, comme nous l’avons fait ci-dessus. Un grand merci à la Flutter Team pour cela : quel gain de temps !
🗒️ Note » Au moment de la rédaction, flutter_localizations prend en charge 78 langues.
🔗 Ressource » La documentation officielle de Flutter explique bien comment les différentes parties, comme les délégués et la classe Localizations, fonctionnent ensemble pour l’i18n/l10n.
La prop 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, générons 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 (importer) 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. C’est exact, 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', ''),
],
Thème : ThemeData(
Palette principale : Colors.blue,
),
// retirer accueil : 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 à la locale active, extraite de notre fichier app_<locale>.arb.
✋ Attention » 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 exécutons notre application, voilà !

Notre titre est maintenant en arabe. De plus, remarquez comment Flutter a automatiquement disposé de nombreux widgets dans une direction de droite à gauche pour nous. Puisque l’arabe est une langue de droite à gauche, cela nous fait gagner beaucoup de temps ! Nous devrons corriger ce rembourrage à gauche de l’image dans les HeroCard, et nous le ferons lorsque nous aborderons la directionnalité un peu plus tard.
🤿 Allez plus loin » Le lecteur aux yeux d’aigle a peut-être remarqué que AppLocalizations.of(context) ressemble beaucoup à l’appel d’un InheritedWidget. C’est parce que les objets de localisation fonctionnent beaucoup comme des InheritedWidgets.
C’est tout pour la configuration. Nous disposons désormais des bases nécessaires à la localisation de notre application. Une question que vous pourriez avoir à ce stade est : « comment Flutter décide-t-il quel paramètre régional utiliser ? » Parlons-en, si vous le voulez bien.
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 (optionnel) — 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 le
code de langue,code de scriptetcode de paysà l’un de ceux desupportedLocales. Si cela échoue, - Essayez de faire correspondre le
code de langueetcode de scriptà l’un de ceux desupportedLocales. Si cela échoue, - Essayez de faire correspondre le
code de langueet lecode de paysavec l’un de ceux desupportedLocales. Si cela échoue, - Essayez de faire correspondre le
code de langueavec l’un de ceux desupportedLocales. Si cela échoue, - Essayez de faire correspondre le
code de paysavec l’un de ceux desupportedLocalesuniquement lorsque tous les paramètres régionaux préférés n’ont pas trouvé de correspondance. Si cela échoue, - Retournez le premier élément de
supportedLocalescomme solution de repli.
Donc, dans notre application, si la langue iOS de l’utilisateur est réglée sur ar_SA, il verrait nos localisations ar (4. ci-dessus). Si la langue iOS de l’utilisateur est réglée sur fr (français), il verrait nos localisations en anglais en (6 ci-dessus). Sur Android, un utilisateur peut avoir une liste de paramètres régionaux préférés, pas seulement un. Cela est couvert par Flutter dans l’algorithme de résolution ci-dessus.
🔗 Ressource » L’algorithme ci-dessus est une reformulation du document officiel 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 repli sans le code pays, comme fr.
Mise à jour du projet iOS
La documentation officielle de Flutter mentionne la nécessité de mettre à jour le 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 l’extrait de code suivant.
Locale activeLocale = Localizations.localeOf(context);
// En supposant que nos paramètres régionaux actifs sont fr_CA...
debugPrint(activeLocale.languageCode);
// => fr
debugPrint(activeLocale.countryCode);
// => CALangage du code : Dart (dart)
✋ Attention » Remarquez que nous utilisons Localizations, un widget intégré à Flutter, et non le 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 de l’icône de la barre d’application de notre HeroList ensuite. Autant en faire bon usage 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(
barre d'application : AppBar(
Titre : Text(title),
actions: <Widget>[
IconButton(
Icône : Icon(Icons.settings),
tooltip: Ouvrir les paramètres,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Settings()),
);
},
)
] ,
),
body: ...
);
}
Langage du code : Dart (dart)
Localisons cette info-bulle. Tout d’abord, nous allons ajouter les entrées pertinentes à nos fichiers ARB.
// English
{
"appTitle": « Héros de l'informatique »,
"openSettings": "Ouvrir les paramètres"
}
// Arabe
{
"appTitle": "أبطال علوم الكمبيوتر",
"openSettings": "إفتح الإعدادات"
}Langage du code : JSON / JSON avec commentaires (json)
Ensuite, rechargeons 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 (

), pas 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: Texte (titre),
actions: <Widget>[
IconButton(
Icône : Icon(Icons.settings),
tooltip : t.openSettings
onPressed: () {
Navigator.push(
Contexte,
MaterialPageRoute(builder: (context) => Settings()),
);
},
)
] ,
),
Corps : ...
);
}
}
Langage du code : Dart (dart)
Avec ce code en place, lorsque nous rechargerons notre application, nous devrions voir la valeur localisée de notre info-bulle dans l’Inspecteur de widget.

✋ Attention » Vous pouvez souvent obtenir mise en évidence des erreurs dans votre IDE après avoir ajouté de nouveaux messages de traduction. Si vous avez rechargé votre application, l’erreur peut être incorrecte (il se peut que tout fonctionne correctement pour vous). Si vous recevez un message indiquant qu’il y a des erreurs de build, vous pouvez essayer de lancer l’application tout de même. Tant que l’application se compile et s’exécute et que vous voyez vos nouvelles traductions, tout va probablement bien. Pour faire disparaître l’erreur dans l’IDE, fermez complètement votre application, puis redémarrez-la.
Interpolation dans les messages
Nous sommes en route 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 août 1950,
bio: Conçu et développé l’Apple I &
« Micro-ordinateurs Apple II. »
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. Nous pouvons utiliser un espace réservé dans nos fichiers de traduction pour y parvenir.
// English
{
// ...
"wozniakBio": "Développé les micro-ordinateurs {appleOne} & {appleTwo}."
"@wozniakBio": {
"placeholders": {
"appleOne": {},
"appleTwo": {}
}
},
// ...
}
// Arabe
{
// ...
"wozniakBio": "A développé les ordinateurs {appleOne} et {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 optionnelles 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).
🗒️ Note » 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)
✋ Heads up » Les noms d’espaces réservés doivent être des noms de paramètres de méthode Dart valides.
Nous pourrions utiliser l’objet espaces réservés 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": {
// Type explicite
"type": String,
// A little doc
"example": "Apple I"
},
// It's perfectly ok to just specifiy the name
"appleTwo": {}
}
}Langage du code : JavaScript (javascript)
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)
🗒️ Note » Il est parfaitement acceptable dans la plupart des cas d’utiliser un {} espace réservé vide pour les définitions d’espace réservé. Une définition vide fera en sorte que le paramètre soit de type Object. Sous le capot, Flutter utilisera simplement la valeur theParameter.toString() du paramètre donnéa, 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().
OK, après avoir relancé l’application pour mettre à jour AppLocalizations, nous pouvons localiser le message de 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 août 1950,
bio : t.wozniakBio('Apple I', 'Apple II'),
imagePath: 'assets/images/steve_wozniak.jpg',
),
// ...
);
}
}
Langage du code : Dart (dart)
Avec cela en place, nous savons que nous ne pouvons jamais être poursuivis par des entreprises de fruits pour avoir mal représenté leurs produits dans n’importe quelle langue.

Pluriels
Nous devons souvent gérer des pluriels dynamiques dans notre traduction. “Vous avez reçu un message” ou “Vous avez reçu 3 messages”, par exemple.
Il est important de noter que différentes langues gèrent les pluriels différemment. Par exemple, l’anglais a deux formes plurielles : un et autre (autre == zéro et >1). L’arabe a six formes plurielles . Cela peut poser quelques difficultés lors de la localisation en utilisant des bibliothèques qui ne prennent pas en charge des règles de pluriel complexes. Heureusement, la solution i18n officielle de Flutter gère les pluriels complexes dès le départ, donc cela répond à nos besoins. Utilisons-le 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{Pas encore de héros} =1{1 héros} other{{count} heroes}}{# héros}}",
"@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.
✋ Attention » : 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 à un message pluriel ; ils sont spécifiés comme d’habitude (voir Interpolation ci-dessus).
Flutter prend en charge les formes plurielles suivantes.
- zero ➞
=0{Pas de héros} - one ➞
=1{Un héros} - two ➞
=2(Deux héros} - few ➞
few{Les {count} héros} - many ➞
many{{count} héros} - other ➞
other{{count} héros}
few, many, et other ont des significations différentes selon la langue active<4>. La seule forme requise dans any langue est la forme other.
🗒️ Remarque » Nous n’avions pas besoin d’utiliser la forme zero =0 dans notre message en anglais ci-dessus. Si nous l’avions omis, Flutter aurait utilisé notre forme other à la place.
D’accord, allons ajouter notre message en arabe. Comme nous l’avons mentionné plus tôt, l’arabe a six formes plurielles.
{
// ...
"heroCount": "{count,plural, =0{Il n'y a pas encore de héros} =1{Un héros} =2{Deux héros}}",
// ...
}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(
// ...
Corps : 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 espace réservé dans les entrées compagnon de notre fichier modèle ARB (anglais dans notre cas). Il n’y a pas vraiment d’endroit idéal pour le formatage des nombres dans notre petite application de démonstration, donc nous allons simplement prétendre que nous avons une application de commerce électronique pour la démonstration.
// app_en.arb dans l'application d'exemple qui a un panier d'achat
{
"itemTotal": "Votre total est : {value}",
"@itemTotal": {
"placeholders": {
"value": {
"type": "double",
"format": "currency"
}
}
}
}
Langage du code : JSON / JSON avec commentaires (json)
Remarquez que 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 dans l'exemple d'application qui a un panier d'achat
{ "itemTotal": "Totala: {value}" }Langage du code : JSON / JSON avec commentaires (json)
Après avoir rechargé notre application, nous pouvons utiliser notre message comme d’habitude.
// Dans un widget
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
// Dans un constructeur de widget avec un contexte
var t = AppLocalizations.of(context);
var message = t.itemTotal(56.12);
// => "Votre total est 56,12 USD" lorsque le paramètre linguistique actuel est anglais
// => "Total : EGP56.12" lorsque les paramètres régionaux actuels sont l'arabe
Langage du code : Dart (dart)
✋ Attention » Vous ne pouvez pas remplacer le nombre formats par paramètres régionaux. Le format que vous spécifiez dans votre modèle de paramètre linguistique (anglais dans notre cas) sera utilisé dans les autres paramètres linguistiques, indépendamment de toute surcharge 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 i18n. Le format de devise que nous avons utilisé ci-dessus est l’un des formats intégrés au formateur de nombres Intl. D’autres formats incluent des décimales, des pourcentages et plus encore.
🔗 Ressource » Consultez le guide de l’utilisateur officiel pour tous les formats disponibles.
Cependant, nous n’avons pas à compter sur Flutter pour passer nos nombres à intl. Nous pouvons utiliser intl directement pour avoir plus de contrôle sur notre format de nombres.
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
// Dans un constructeur de widget avec un contexte
var currentLocale = AppLocalizations.of(context).localeName;
var compact = NumberFormat.compact(locale: currentLocale).format(6000000);
// => "6M" lorsque le paramètre linguistique actuel est l'anglais américain
// => "٦ مليون" lorsque les paramètres régionaux actuels sont l'arabe égyptien
var simpleCurrency = NumberFormat.simpleCurrency(locale: currentLocale).format(14.24);
// => « $14.24 » lorsque le paramètre linguistique actuel est l’anglais (États-Unis)
// => "ج.م. ١٤٫٢٤" lorsque le paramètre linguistique actuel est l'arabe égyptienLangage du code : Dart (dart)
🔗 Ressource » Vous n’avez pas besoin d’utiliser les formats prédéfinis : compact et simpleCurrency. Le constructeur intl NumberFormat vous donne un contrôle précis sur vos formats de nombres. Lisez tout à ce sujet dans la documentation officielle.
✋ Attention » 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.
✋ Attention » 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 l’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éesa ce qui n’est pas très cool.

Rappelez-vous que nous rendons chacun de nos héros avec 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 ? '' : 'Né $born',
// ...
),
),
// ...
),
);
}
}Langage du code : Dart (dart)
Pour formater la date born pour chaque paramètre régional que notre application prend en charge, nous ajoutons d’abord quelques nouveaux messages localisés avec des valeurs de date interpolées.
Anglais
{
// ...
"heroBorn": "Né {date}",
"@heroBorn": {
"placeholders": {
"date": {
Type : "DateTime",
"format": "yMMMd"
}
}
},
// ...
}
// Arabe
{
// ...
"heroBorn": "تاريخ الميلاد {date}",
}Langage du code : JSON / JSON avec commentaires (json)
Lorsque nous définissons notre espace réservé date dans notre fichier de localisation de modèle, nous devons lui attribuer le type DateTime. Nous pouvons ensuite utiliser un format pour spécifier comment nous voulons afficher la date. Le format yMMMd que nous avons défini ci-dessus signifie «année, mois abrégé, jour», ce qui en anglais américain donnerait quelque chose comme «Dec 9, 1906».
🔗 Resource » En fait, en coulisses, Flutter utilise simplement la classe DateFormat d’intl : 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. Vérifiez-les dans la documentation officielle de DateFormat.
🗒️ Note » Nous n’étions pas obligés d’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.
D’accord, 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; // Date et heure de naissance
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, bas : 4,
Enfant : Texte(
born.isEmpty ? '' : t.heroBorn(bornDateTime),
// ...
),
),
// ...
),
);
}
}Langage du code : Dart (dart)
On nous remet la date de naissance d’un héros sous la forme d’une chaîne, donc nous devons d’abord la parser en DateTime. Nous utilisons la classe DateFormat d’intl pour le faire dans le constructeur de notre widget.
Dans la méthode build, nous passons simplement le DateTime analysé à notre message localisé t.heroBorn() . Cela nous donne des dates bien localisées.


Que faire si nous ne voulons pas utiliser l’un des squelettes prédéfinis et personnaliser complètement nos formats de date ? Eh bien, tout comme le formatage des nombres (voir ci-dessus), nous devrions utiliser directement la classe intl.DateFormat. Supposons que nous voulions afficher les dates de naissance de notre héros dans un format comme « 1912-06-23 ». Nous procéderions comme suit.
import 'package:intl/intl.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
// Dans le constructeur de widget avec le contexte
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" en Anglais américain
// => "١٩١٢-٠٦-٢٣" en arabe égyptien
Langage du code : JavaScript (javascript)
Nos messages localisés heroBorn dans nos fichiers ARB prendraient alors simplement des paramètres réguliers Object ou chaîne, puisque nous avons déjà fait le formatage pour eux.
Directionnalité : de gauche à droite et de droite à gauche
Alors que l’anglais est une langue de gauche à droite (LTR), l’arabe va dans l’autre sens et est disposé de droite à gauche (RTL). Cela pose actuellement un problème pour nous 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 la marge intérieure autour de notre image.
import 'package:intl/intl.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
// Dans le constructeur de widget avec le contexte
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" en Anglais américain
// => "١٩١٢-٠٦-٢٣" en arabe égyptien
Langage du code : Dart (dart)
Cela fonctionne dans les langues écrites de gauche à droite (LTR), où nous voulons une marge à droite entre l’image et le texte. Dans les langues RTL, cependant, nous voulons la marge à gauche.
Un remède facile ici est d’utiliser EdgeInsetsDirectional au lieu de EdgeInsets.
//...
class HeroCard extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
// ...
return Card(
Enfant : Padding(
padding : const EdgeInsets.all(4.0),
Enfant : ligne(
crossAxisAlignment : CrossAxisAlignment.start ,
enfants : <Widget>[
Padding(
padding: const EdgeInsetsDirectional.only(end: padding: const EdgeInsetsDirectional.only(end: 8.0),
child: ClipRRect(
// Portrait...
),
),
Expanded(
// Widgets de texte...
),
],
),
),
);
}
}Langage du code : PHP (php)
Remarquez que nous utilisons end au lieu de right pour définir le rembourrage entre l’image et le texte. EdgeInsetsDirectional est l’un des quelques widgets de mise en page Flutter qui sont conscients de la direction des paramètres régionaux. Ces widgets prennent les paramètres start et end au lieu de left et right. Et le bon côté, c’est que ces widgets directionnels feront automatiquement la bonne chose pour les paramètres régionaux actifs :
start==leftpour les langues de gauche à droite (LTR)start==rightpour les langues RTLend==droitepour les langues LTRend==gauchepour les langues RTL
Avec ce petit ajustement du code, notre problème de mise en page est résolu.

🔗 Ressource » Au moment de la rédaction, la documentation officielle de Flutter répertorie les widgets directionnels suivants :
- EdgeInsetsDirectional
- AlignmentDirectional
- BorderDirectional
- BorderRadiusDirectional
- PositionedDirectional
- AnimatedPositionedDirectional
Avec tout cela en place, notre application finale a vraiment l’air d’être globalisée.

🔗 Ressource » 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 différents paramètres régionaux ou 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 devrions 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 des images de drapeaux pour l’Égypte et les États-Unis. Le fichier directement dans le dossier images (sans paramètres régionaux) est une image de secours pour d’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/';
// Lorsque le code du pays n'est pas pris en charge, nous
// afficher l'image de secours.
if (locale.countryCode?.isEmpty == true) {
return basePath + 'flag.jpg';
}
String localePath = '${locale.countryCode!.toLowerCase()}/';
return basePath + localePath + imageName;
}
return Scaffold(
Corps : Padding(
padding: const EdgeInsets.all(16),
Enfant : Column(
Enfants : [
// ...
Padding(
padding: const EdgeInsets.only(bottom: padding: const EdgeInsets.only(bottom: 2.0),
Enfant : 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});
// Ce widget est la racine de votre application.
@override
Widget build(BuildContext context) {
return MaterialApp(
...
supportedLocales: [
const Locale('ar', ''),
const paramètres régionaux('en', ''),
// Ajouter les codes de région pris en charge
const Locale('ar', 'EG'),
const paramètres régionaux('en', 'US'),
],
}
}Langage du code : Dart (dart)
Maintenant, l’application affiche l’actif approprié pour les paramètres régionaux de l’utilisateur et affiche une solution de repli si ses paramètres régionaux ne sont pas pris en charge.

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 accommoder cela, ajoutons un sélecteur de langue dans notre application. Cela devrait fonctionner 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());
}
// Nous devons rendre MyApp Stateful car
// il doit réagir lorsque les paramètres régionaux changent.
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
État<MyApp> créerÉtat() => _MyAppState();
static void setLocale(BuildContext context, Locale newLocale) {
_MyAppState? state = context.findAncestorStateOfType<_MyAppState>();
state?.setLocale(newLocale);
}
}
class _MyAppState extends State<MyApp> {
Paramètres régionaux ? _locale;
setLocale(paramètres régionaux locale) {
setState(() {
_locale = paramètres régionaux;
});
}
// Ce widget est la racine de votre application.
@override
Widget build(BuildContext context) {
return MaterialApp(
// ...
paramètres régionaux : _locale,
initialRoute: '/',
routes: {
'/' : (contexte) {
return HeroList(title: 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);
// Options du menu déroulant
var items = [
'en',
ar,
];
return Scaffold(
Barre d'application : AppBar(
Titre : Texte (titre) ,
actions : <Widget>[
DropdownButton(
// Icône de flèche vers le bas
icon: const Icon(Icons.settings, color: Colors.white,), Colors.white,),
éléments : items.map((chaîne items) {
return DropdownMenuItem(
valeur : éléments,
Enfant : Texte (items) ,
}).toList(),
onChanged : (chaîne? newValue) {
MyApp.setLocale(context, Locale(newValue));
},
),
],
),
// ...
);
}
}Langage du code : Dart (dart)
Avec cela, l’utilisateur peut sélectionner la langue de l’application indépendamment de la langue du système.
🗒️ Note » Avant de sélectionner un paramètre régional, il sera défini sur le paramètre régional par défaut du système de l’utilisateur.

🗒️ Note » Avant que l’utilisateur ne sélectionne manuellement un paramètre régional dans l’application, le paramètre régional du système est utilisé par défaut. Cependant, le paramètre linguistique sélectionné manuellement par l’utilisateur dans l’application ne persistera pas lorsque l’application sera redémarrée. Pour conserver la préférence de paramètres régionaux de l’utilisateur dans l’application, vous devez enregistrer et récupérer ces paramètres en utilisant par exemple le plug-in Shared Preferences.
Le projet final est disponible sur GitHub.
Localisation Flutter simplifiée
Nous espérons que vous avez apprécié notre tutoriel de localisation Flutter et que vous avez appris quelques astuces pratiques. Maintenant, si vous êtes prêt à passer au niveau supérieur en matière de localisation, Phrase Strings est votre solution de référence. Un 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 le code que vous affectionnez tant. Découvrez toutes les fonctionnalités de Phrase pour les développeurs et constatez par vous-même comment elles peuvent vous aider à internationaliser vos applications plus rapidement.

