Rien n’est aussi frustrant que d’avoir du nouveau contenu prêt à être diffusé dans votre application mobile et de devoir attendre l’approbation du Play Store ou de l’App Store (Oui, je m’adresse particulièrement à vous, Apple) pour que vos utilisateurs puissent y accéder.
Grâce à la technologie Phrase Over the Air, il vous suffit d’un clic pour intégrer de nouvelles traductions à votre application en direct. Zéro complication : pas de version à publier, pas d’approbation de la boutique. Ajoutez à cela l’expérience fluide des développeurs et la prise en charge multi-plateforme de Flutter et vous déployez des fonctionnalités et du contenu à votre public à une vitesse fulgurante.
Dans ce guide pratique, nous allons localiser une petite application Flutter, la connecter à l’interface CLI Phrase, puis à Phrase Over the Air (OTA). N’hésitez pas à passer directement à la partie de votre choix, en fonction de vos besoins.
L’application de démonstration
Cette modeste application s’intitule Heroes of Computer Science. Nous avons déjà présenté cette application dans notre Guide de la localisation Flutter, un tutoriel approfondi sur l’internationalisation du contenu Flutter. Nous allons d’abord brièvement passer en revue l’application.
🔗 Ressources » Obtenez le code complet de notre application de démonstration sur GitHub.

Comme vous pouvez l’imaginer, l’application est assez simple :
.
└── lib/
├── main.dart (MaterialApp)
└── features/
└── heroes/
├── hero_list.dart (HeroList)
└── hero_card.dart (HeroCard)
import 'package:flutter/material.dart';
import 'package:flutter_phrase_ota_2021/features/hereos/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'),
);
}
}
import 'package:flutter/material.dart';
import './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),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text('6 Hereos'),
),
Expanded(
child: ListView(
children: <Widget>[
HeroCard(
name: 'Grace Hopper',
born: '9 December 1906',
bio: 'Devised theory of machine-independent...',
imagePath: 'assets/images/grace_hopper.jpg',
),
HeroCard(
name: 'Alan Turing',
born: '23 June 1912',
bio: 'Father of theoretical computer science...',
imagePath: 'assets/images/alan_turing.jpg',
),
// ...
],
),
),
],
),
),
);
}
}
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),
),
],
),
),
],
),
),
);
}
}
Voilà pour notre application de démonstration. Nos chaînes sont actuellement codées en dur, ce qui est loin d’être idéal pour la localisation. Nous allons donc internationaliser notre application.
🗒 Remarque » Si vous souhaitez coder avec nous à partir d’ici, il vous suffit de cloner la branche de départ du référentiel de l’application.
Versions utilisées
Dans cet article, nous utilisons les versions suivantes (langues, framework et packages) :
- Dart SDK 2.13.4
- Flutter 2.2.3
- flutter_localizations (la version semble liée à Flutter) — fournit des localisations pour des widgets courants, comme Material ou Cupertino.
- intl 0.17.0 — la colonne vertébrale du système de localisation ; nous permet de créer et d’utiliser nos propres localisations ; sert pour le formatage des dates et des nombres ; nécessaire pour Phrase Flutter SDK.
- Phrase 1.0.0 — Phrase Flutter SDK ; nous permet de nous connecter à Phrase OTA.
- flutter_dotenv 5.0.0 — facilite l’utilisation des fichiers de configuration .env afin que nous puissions garder les clés hors de notre dépôt Git.
Localisation de notre application Flutter
Nous allons utiliser le puissant package officiel de localisation de Flutter pour localiser rapidement notre application.
🗒 Remarque » Si vous avez déjà une application Flutter localisée, n’hésitez pas à passer à Traductions en over-the-air avec Phrase. Assurez-vous simplement que vous avez localisé avec intl ^0.17.0 et flutter_localizations et tout devrait fonctionner.
Installation des paquets
Nous allons mettre à jour pubspec.yaml pour installer les packages.
# ...
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.17.0
# ...
Enregistrer pubspec.yaml devrait déclencher l’IDE pour que les nouveaux paquets soient installés automatiquement. Si cela ne fonctionne pas, nous pouvons ouvrir une fenêtre de terminal, naviguer jusqu’à la racine de notre projet et exécuter ce qui suit.
flutter pub get
✋🏽 Avertissement » Le SDK Phrase Flutter, qui se connecte à Phrase OTA, nécessite la version 0.17.0 du package intl. Assurez-vous d’utiliser cette version dans votre pubspec.yaml.
Le package de localisation Flutter utilise la génération de code, créant des fichiers dart fortement typés qui correspondent à nos fichiers de traduction. Pour activer cette génération de code, nous devons ajouter une ligne à pubspec.yaml.
# ... flutter: generate: true # ...
🔗 Ressources » Si vous souhaitez en savoir plus sur la manière dont fonctionne la bibliothèque de localisation Flutter, consultez notre article, Guide de la localisation Flutter.
Configuration
Nous devons ajouter un fichier qui permet à la bibliothèque de localisation Flutter de savoir où trouver nos fichiers de traduction et de générer son code dart.
# Où trouver les fichiers de traduction arb-dir: lib/l10n # Traduction par défaut/modèle template-arb-file: app_en.arb # Comment nommer les fichiers dart générés output-localization-file: app_localizations.dart
🔗 Ressources » Le Guide utilisateur officiel d’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.
Configuration de notre application pour iOS
Une étape de plus est nécessaire pour iOS : si nous n’ajoutons pas nos paramètres régionaux pris en charge au fichier iOS Info.plist, cela pourrait ne pas fonctionner comme attendu.
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>ar</string>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<!-- ... -->
Pour notre démo, nous prendrons en charge l’anglais et l’arabe. Bien sûr, vous pouvez ajouter les langues que vous souhaitez prendre en charge.
Ajout des fichiers de traduction
Ensuite, nous devrons ajouter nos fichiers ARB (Application Resource Bundle) de traduction. Ceux-ci seront utilisés par la bibliothèque de localisation Flutter pour générer du code Dart. Les fichiers ARB contiennent simplement des paires clé JSON/valeur classiques. Nous copierons toutes les chaînes codées en dur dans notre application vers nos fichiers de traduction.
{
"appTitle": "Heroes of Computer Science",
// ...
"hopperName": "Grace Hopper",
"hopperBio": "Devised theory of machine-independent programming languages.",
"turingName": "Alan Turing",
"turingBio": "Father of theoretical computer science & artificial intelligence.",
// ...
}
{
"appTitle": "أبطال علوم الكمبيوتر",
// ...
"hopperName": "جريس هوبر",
"hopperBio": "إبتكرت نظرية للغات البرمجة المستقلة عن الجهاز.",
"turingName": "آلان تورينج",
"turingBio": "الأب الروحي لعلوم الكمبيوتر النظرية والذكاء الاصطناعي.",
// ...
}
Encore une fois, nous avons ajouté des traductions en anglais et en arabe pour notre démo. N’hésitez pas à utiliser nos paramètres régionaux ou les vôtres ici.
🗒 Remarque » Nous devons nous assurer que nos noms de dossiers et de fichiers correspondent à la configuration que nous avons placée dans l10n.yaml ci-dessus.
Génération du code
C’est fini pour les traductions. Nous pouvons commencer la génération du code. Tout d’abord, configurons MaterialApp pour la localisation.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_phrase_ota_2021/features/hereos/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: [
Locale('en', ''),
Locale('ar', ''),
],
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HeroList(title: 'Heroes of Computer Science'),
);
}
}
Maintenant, nous pouvons exécuter l’application pour générer le code dart de localisation. Après avoir exécuté l’application, nous devrions voir les fichiers suivants dans notre projet.
.
└── .dart_tool/
└── flutter_gen/
└── gen_l10n/
├── app_localizations.dart
├── app_localizations_en.dart
└── app_localizations_ar.dart
Si vous voyez les fichiers générés, c’est que tout a fonctionné !
Localiser l’application
Nous allons maintenant nettoyer main.dart et le localiser. Nous allons retirer l’import direct de flutter_localizations et utiliser à la place AppLocalizations que nous avons généré. Nous commencerons également à utiliser nos localisations via AppLocalizations.of(context) pour intégrer nos traductions.
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_phrase_ota_2021/features/hereos/hero_list.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',
routes: {
'/': (context) =>
HeroList(title: AppLocalizations.of(context)!.appTitle)
},
);
}
}
Vous avez peut-être remarqué que nous utilisons les propriétés onGenerateTitle, initialRoute, et routes de MaterialApp au lieu de title et home. C’est parce que le chargement de la bibliothèque de localisation Flutter est une opération asynchrone : nous n’aurons pas de traductions disponibles tant que notre MaterialApp est en cours de construction. Nous utilisons des alternatives pratiques de rappel pour fournir des traductions dès qu’elles sont prêtes.
Nous allons également localiser nos widgets HeroList et HeroCard.
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import './hero_card.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),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(t.heroCount(6)),
),
Expanded(
child: ListView(
children: <Widget>[
HeroCard(
name: t.hopperName,
born: '9 December 1906',
bio: t.hopperBio,
imagePath: 'assets/images/grace_hopper.jpg',
),
HeroCard(
nom : t.turingName,
born: '23 June 1912',
bio: t.turingBio,
imagePath: 'assets/images/alan_turing.jpg',
),
// ...
),
],
),
),
],
),
),
);
}
}
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
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';
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)!;
var theme = Theme.of(context);
return Card(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
// <Already-localiazed image from this.imagePath>...
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
// <Already-localized name from this.name>...
),
Padding(
padding: const EdgeInsets.only(top: 2, bottom: 4),
child: Text(
born.isEmpty ? '' : t.heroBorn(bornDateTime),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w300,
),
),
),
// <Already-loclalized bio from this.bio>...
],
),
),
],
),
),
);
}
}
🔗 Ressources » Si vous vous demandez à quoi sert l’appel t.heroCount(6), lisez tout à ce sujet dans notre Guide de la localisation Flutter.
Maintenant, lorsque nous définissons les paramètres régionaux de notre système d’exploitation mobile pour l’arabe, nous pouvons voir nos chaînes traduites. Youpi 😃 !

Connexion à Phrase
Maintenant que notre application est localisée, nous allons la connecter à Phrase.
🗒 Remarque » Si vous souhaitez coder avec nous à partir d’ici, il vous suffit de cloner la branche localisée de notre dépôt GitHub compagnon.
Création d’un projet Phrase
🗒 Remarque » Si vous avez un projet Phrase connecté à notre application Flutter, passez directement à Traductions over-the-air avec Phrase.
Je suppose que vous avez un compte Phrase à ce stade. Si ce n’est pas le cas, s’inscrire pour un essai gratuit.
Tout d’abord, nous allons créer un nouveau projet Phrase en nous connectant et en allant sous Projects ➞ Create New Project.

Cela ouvrira la boîte de dialogue Add Project. Ici, vous saisirez un nom de projet. Tout ce qui n’est pas le nom du projet est facultatif : nous gagnerons du temps plus tard si nous spécifions ARB comme notre format principal. Lorsque nous sommes satisfaits de nos options, nous pouvons cliquer sur le bouton Save.

Ensuite, nous allons accéder à la page de configuration du projet. Cliquez sur le bouton Set up languages pour ajouter les langues prises en charge par notre application.


Nous pouvons ajouter autant de langues que nous le souhaitons ici. La première sera la langue par défaut. Après avoir ajouté les paramètres régionaux, cliquez sur Create languages. Nous serons ramenés à la page de configuration du projet. C’est tout pour la configuration à ce stade. Cliquez sur le bouton Skip setup pour continuer.

À ce stade, notre projet Phrase est prêt à l’emploi. Tant que nous y sommes, obtenons un jeton d’accès; nous en aurons besoin pour connecter notre projet Flutter au projet Phrase.
Obtenir un jeton d’accès Phrase
Pour générer un jeton d’accès, dirigeons-nous près du coin supérieur droit de l’écran où se trouve notre nom. Cliquez sur notre nom pour ouvrir un menu déroulant avec une option Access Tokens et cliquez dessus.


Cela ouvre la page Jetons d’accès, révélant un bouton Générer un jeton. Cliquez sur ce bouton pour ouvrir la boîte de dialogue Generate Token.

Nous devons juste donner à notre jeton une note pour nous souvenir de la raison pour laquelle nous l’avons créé. Nous voulons à la fois les autorisations lecture et écriture pour notre projet, car nous allons faire une synchronisation bidirectionnelle entre notre projet et Phrase. Choisissez les options qui ont du sens pour votre projet et cliquez sur Enregistrer pour révéler le jeton.

✋🏽 Avertissement » Copiez le jeton dans un endroit sûr : une fois que vous quittez la page des jetons, vous ne pourrez plus accéder au jeton depuis la console de Phrase.
Installation du CLI
Avec le jeton en main, nous pouvons nous rendre à notre projet Flutter pour le connecter à Phrase. Nous aurons besoin de Phrase CLI pour cela, alors assurez-vous de l’installer. Je suis sur macOS et j’aime utiliser le gestionnaire de paquets Homebrew, donc je vais prendre cette voie pour installer le CLI. Je vais simplement exécuter la commande suivante dans un terminal.
brew install phrase
Si tout se passe bien, vous devriez obtenir quelque chose comme ceci :

🔗 Ressources » Si vous n’êtes pas sur macOS ou si vous ne souhaitez pas utiliser Homebrew, consultez la documentation sur l’interface CLI de Phrase pour connaître toutes les options d’installation disponibles.
Connexion de notre application Flutter avec Phrase
Maintenant que nous avons installé le Phrase CLI, nous pouvons connecter notre projet Phrase à notre projet Flutter. Ouvrons une ligne de commande, naviguons à la racine de notre projet Flutter et exécutons ce qui suit.
Phrase init

Après avoir entré le jeton d’accès que nous avons généré plus tôt et cliqué sur Enter, nous serons invités à sélectionner notre projet Phrase.

Entrons le numéro du projet approprié et appuyons sur Enter. On nous demandera ensuite le format à utiliser. Si nous avons sélectionné ARB lors de la création de notre projet Phrase dans la console Phrase, ce format devrait être proposé par défaut, et nous pouvons simplement appuyer sur Enter. Sinon, nous pouvons sélectionner 1. (arb) dans la liste.

On vous demandera maintenant d’entrer les chemins de vos fichiers de traduction, relatifs à la racine du projet. Ces chemins utilisent un espace réservé spécial, <locale_name>, qui correspond aux codes de paramètres régionaux dans notre projet. Par exemple, app_<locale_name>.arb indiquera à Phrase de s’attendre à des fichiers app_en.arb et app_ar.arb, puisque nous avons ajouté ces deux paramètres régionaux à notre projet Phrase.

Pour les chemins source et cible, entrons ./lib/l10n/app_<locale_name>.arb. Vous vous souviendrez que cela correspond au chemin du fichier de traduction dans notre projet Flutter.
Enfin, une invite vous demandera si vous souhaitez charger vos fichiers de traduction sur Phrase pour la première fois. Pour ce faire, saisissez y, puis appuyez sur Enter.
🗒 Remarque » Si vous manquez l’étape de chargement lors de l’initialisation, exécutez simplement phrase push depuis la ligne de commande pour charger les fichiers.
À ce stade, si tout s’est bien passé, un fichier .phrase.yml sera apparu à la racine de notre projet.
✋🏽 Avertissement » Il est plus sûr d’ajouter .phrase.yml à notre .gitignore pour éviter que les secrets d’accès au projet ne se retrouvent dans notre dépôt Git.
Si vous avez choisi l’option de chargement plus tôt, vous pouvez naviguer vers votre projet Phrase, aller sous Languages et ouvrir une langue pour voir vos traductions prêtes à être modifiées.

Traductions over-the-air avec Phrase
À ce stade, notre projet peut synchroniser les traductions dans les deux sens avec Phrase. Cependant, vous devriez toujours créer une nouvelle version de votre application mobile à chaque mise à jour de traduction. Cela signifie attendre l’approbation de l’application et que les téléphones de vos utilisateurs se mettent à jour vers la dernière version de l’application. Il existe une meilleure façon : Over-the-air.
Comment fonctionne Phrase OTA
En résumé, après avoir connecté votre projet à Phrase OTA et déployé une nouvelle version de votre application avec OTA activé, voici ce qui se passe :
- Un utilisateur ouvre votre application, déclenchant une récupération OTA de la traduction en arrière-plan.
- La prochaine fois que l’utilisateur ouvre l’application, il voit vos nouvelles traductions. Il n’était pas nécessaire de mettre à jour vers une nouvelle version de l’application pour ce faire. Cela se produit « over-the-air ».

Plutôt génial, pas vrai ? Alors, qu’attendez-vous pour connecter votre projet à Phrase OTA ?
Installation du SDK Phrase Flutter
Tout d’abord, nous allons ajouter le package Phrase Flutter à notre projet en mettant à jour notre pubspec.yaml.
# ...
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.17.0
phrase: ^1.0.0
# ...
Notre IDE devrait avoir installé le paquet automatiquement. Sinon, vous pouvez ouvrir une ligne de commande, naviguer jusqu’à la racine de votre projet et exécuter ce qui suit.
flutter pub get
Le SDK Phrase doit générer du code pour vous. Pour lui demander de générer le code, exécutez la commande suivante depuis la racine de votre projet.
flutter pub run phrase
Si tout s’est bien passé, vous verrez un joli message de succès.

✋🏽 Avertissement » Le package Flutter de Phrase installe un plug-in Flutter, donc vous aurez besoin de CocoaPods installé et à jour si vous voulez exécuter votre projet Flutter sur iOS.
🔗 Ressources » Plus d’informations sur notre SDK Flutter sont disponibles dans notre Centre d’aide.
Créer une distribution
Pour rendre les traductions mises à jour disponibles pour votre application over-the-air, vous devrez disposer d’une distribution OTA. Connectez-vous à votre console Phrase et rendez-vous sur Over-the-air.


Cela ouvrira la page Over the Air avec un joli bouton Créer une distribution. Cliquez sur le bouton pour ouvrir la boîte de dialogue Add distribution.

Vous donnerez un nom à votre distribution et la connecterez au projet Phrase que vous avez créé précédemment. La seule plateforme que vous couvrez ici est Flutter, donc vous sélectionnerez cette option comme unique possibilité sous Plateformes. Laissez les paramètres par défaut sous Paramètres, car ils vous conviennent. N’hésitez pas à les modifier pour répondre à vos besoins.
🗒 Remarque » Vous ne pouvez pas modifier les Plateformes que votre distribution cible après l’avoir créée. Vous pouvez cependant modifier la distribution Settings à tout moment.
Cliquez sur Save lorsque vous êtes satisfait de votre configuration pour valider vos modifications et ouvrir la page des détails de la distribution.

Les trois clés, Distribution ID, Development Secret et Production Secret, sont essentielles pour connecter votre projet Flutter à cette distribution OTA, et vous les utiliserez dans un instant.
🗒 Remarque » Ne vous inquiétez pas : vous pouvez accéder aux clés de distribution à tout moment.
Créer une version de développement
Maintenant que vous avez une distribution, vous aurez besoin d’une release OTA. Une version est simplement un instantané des mises à jour de traduction que vous souhaitez rendre disponibles à vos utilisateurs d’application mobile.
Tout d’abord, mettez à jour vos traductions afin d’avoir quelque chose à publier. Rendez-vous dans Projects ➞ <Notre projet> ➞ Languages ➞ en, et mettez à jour la chaîne appTitle.

Maintenant, vous pouvez créer une version. Allez sous ➞ Over-the-Air et cliquez sur le nom de votre distribution.

Le bouton Create release en bas de la page ouvrira la boîte de dialogue Add release.

Pour plus de simplicité, nous allons rendre notre version accessible à tous les utilisateurs. C’est le paramètre par défaut, donc vous n’avez pas besoin de modifier quoi que ce soit dans la boîte de dialogue. Cependant, il est recommandé d’ajouter une description. Une fois satisfait, cliquez sur Save pour créer la version.
Notez que la section Releases en bas de la page comporte une nouvelle ligne avec une version non publiée. Publier une version la rend accessible à vos utilisateurs de production, donc vous voudrez probablement la laisser non publiée pendant vos tests.

✋🏽 Avertissement » Même pour votre environnement de développement, vous devez toujours créer une version pour voir les mises à jour de traduction Phrase reflétées over-the-air dans votre application.
Ajout de Phrase à main.dart
Maintenant que vous avez une version OTA visible dans votre application, terminez la connexion de votre application à Phrase OTA. Mettez à jour votre main.dart pour initialiser le SDK Phrase et assurez-vous d’utiliser Phrase lors de l’accès à vos traductions.
import 'package:flutter/material.dart';
import 'package:phrase/phrase.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_gen/gen_l10n/phrase_localizations.dart';
import 'package:flutter_phrase_ota_2021/features/hereos/hero_list.dart';
void main() {
Phrase.setup(
Identifiant de diffusion
'44****************************ed',
// Clé secrète de développement
'in***************************************no',
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,
Délégués de localisation : PhraseLocalizations.localizationsDelegates,
Langues prises en charge : PhraseLocalizations.supportedLocales ,
theme: ThemeData(
primarySwatch: Colors.blue,
),
Itinéraire initial : '/',
routes: {
'/': (context) =>
HeroList(title: AppLocalizations.of(context)!.appTitle
},
);
}
}
C’est tout ce que nous avons à faire. Nous pouvons continuer à accéder à nos traductions comme d’habitude avec AppLocalizations.of(context). Le SDK Phrase aura modifié les choses en coulisses afin que nos traductions soient extraites de nos mises à jour OTA lorsque cela est approprié.
Nous allons maintenant exécuter l’application. Veuillez noter la console de débogage.

Phrase n’a pas trouvé de cache de traductions OTA, c’est normal. Il en a créé un et a récupéré nos traductions over-the-air pour le remplir. En attendant, Phrase a utilisé nos traductions locales, donc nous n’avons constaté aucun changement dans l’application. Encore une fois, c’est normal.

Mais ces nouvelles traductions brillantes attendent en coulisses et apparaîtront lors du prochain lancement de l’application. Vous ne me croyez pas ? Relancez l’application.

C’est un redémarrage complet, pas un rechargement à chaud
Veuillez noter que la traduction mise à jour de appTitle est maintenant affichée.

Nous n’avons pas eu besoin de faire un phrase pull pour obtenir ces traductions dans notre application.
✋🏽 Avertissement » Tout comme le package de localisations Flutter, le SDK Flutter de Phrase peut perturber votre éditeur de code avec des fichiers et des jetons apparemment manquants. Essayez toujours l’option Debug anyway lorsque vous exécutez l’application après des erreurs comme celle-ci pour vérifier si votre application fonctionne correctement. Si c’est le cas, vous n’avez probablement rien à craindre, et un ou deux redémarrages de l’éditeur de code devraient faire disparaître les erreurs. C’est juste un effet secondaire malheureux de la génération de code en ce moment, mais il n’y a pas de réel dommage.
L’OTA est désormais configuré dans notre projet ! Hourra 🚀 !
Cacher nos clés secrètes
Nous ne voulons probablement pas que nos secrets de distribution OTA se trouvent dans notre dépôt Git. Il serait également agréable que notre application utilise le secret approprié en fonction de l’environnement dans lequel elle s’exécute (développement ou production). Nous pouvons résoudre ces deux problèmes avec un petit module de configuration et un bon vieux fichier .env.
Commençons par installer un petit paquet qui gère le chargement et l’analyse des fichiers .env pour nous. Le package en question est flutter_dotenv, et nous allons l’ajouter à notre pubspec.yaml pour l’installer.
# ...
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.17.0
phrase: ^1.0.0
flutter_dotenv: ^5.0.0
# ...
Ajout d’une classe .env
Avec flutter_dotenv installé, ajoutons un fichier .env à la racine de notre projet pour abriter les clés de notre application.
PHRASE_OTA_DISTRIBUTION_ID=44************************ed PHRASE_OTA_DEV_SECRET=in***********************************no PHRASE_OTA_PRODUCTION_SECRET=EN***********************************gs
Vous vous souviendrez que nous obtenons nos clés de distribution à partir de la page des détails de distribution sur la console Phrase.

Nous devons ajouter le fichier .env à notre liste d’actifs (« liste ») dans pubspec.yaml afin qu’il soit disponible pour notre application.
# ...
assets:
- .env
- assets/images/placeholder.jpg
- assets/images/alan_turing.jpg
## ...
✋🏽 Avertissement » Assurez-vous d’ajouter votre fichier .env à .gitignore pour qu’il ne soit pas inclus dans votre dépôt Git.
Une petite classe Config
Maintenant, créons une petite classe wrapper qui charge le fichier .env et nous donne accès aux clés Phrase OTA qu’il contient.
import 'package:flutter/foundation.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class Config {
static const String fileName = '.env';
static Future load() async => await dotenv.load(fileName: fileName);
static String? get(String key) => dotenv.env[key];
static String? get phraseOtaDistributionId =>
get('PHRASE_OTA_DISTRIBUTION_ID');
// kDebugMode and kRelease mode are constants
// built into foundation.dart (imported above)
static String? get phraseOtaSecret {
if (kDebugMode) {
return get('PHRASE_OTA_DEV_SECRET');
} else if (kReleaseMode) {
return get('PHRASE_OTA_PRODUCTION_SECRET');
}
}
}
Nous avons maintenant une petite API de configuration propre pour charger et avoir accès aux clés secrètes de notre application.
import 'package:flutter/material.dart';
import 'package:phrase/phrase.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_gen/gen_l10n/phrase_localizations.dart';
import 'package:flutter_phrase_ota_2021/services/config/config.dart';
import 'package:flutter_phrase_ota_2021/features/hereos/hero_list.dart';
// Le chargement du fichier .env est une opération asynchrone,
// donc nous devons rendre notre fonction main() asynchrone.
Future<void> main() async {
await Config.load();
Phrase.setup(
Config.phraseOtaDistributionId!,
Config.phraseOtaSecret!,
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// ...
}
Notre application utilisera maintenant automatiquement le secret de développement OTA en mode débogage, et le secret de production en mode production. Pratique. 😉
Création d’une version de production
Il ne serait pas très utile d’avoir des traductions qui arrivent sur nos machines de développement over-the-air et pas pour nos utilisateurs. Une fois que nous sommes satisfaits d’une version, nous pouvons la publier dans notre application pour le bénéfice de nos utilisateurs.
Voici une version release/production de notre application déployée sur un appareil physique.

Rappelez-vous que notre version OTA a mis à jour le titre de l’application pour qu’il soit « Comp-Sci Champs ». Nous ne voyons pas cette mise à jour ici car notre version OTA n’est pas publiée, donc elle n’est pas disponible pour la mise en production.
Nous allons libérer la bête. Dans la console Phrase, rendez-vous sousOver the Air et cliquez sur le nom de notre distribution pour ouvrir la page des détails. En faisant défiler vers le bas, nous trouverons la section Releases contenant une ligne avec notre version, et un joli bouton Publish prêt à être cliqué.

Lorsque nous cliquons sur Publish,, nous obtenons une boîte de dialogue de confirmation ; en confirmant, vous obtenez un message indiquant que la version a été publiée avec succès.
✋🏽 Avertissement » Pour Android, nous devons mettre à jour notre manifeste d’application pour permettre l’accès à Internet en mode release. Sinon, le SDK Phrase ne pourra pas récupérer nos versions OTA en mode de publication / production.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter_phrase_ota_2021">
<uses-permission android:name="android.permission.INTERNET"/>
<application
<!-- ... -->
Maintenant, lorsque nos utilisateurs redémarrent notre application, ils recevront les nouvelles traductions livrées directement depuis Phrase. Pas de gestion des versions d’application, pas d’attente pour Apple. Déploiement semblable au web pour les applications mobiles 🏄.

À noter :
- Phrase OTA ne récupérera que les traductions pour la langue active afin de préserver la bande passante. Cela devrait être transparent pour nos utilisateurs, mais bon à savoir pour nous, développeurs.
- Phrase identifie notre environnement d’application comme développement ou production via le secret de développement ou le secret de production, respectivement. Cela peut être pratique en développement puisque nous pouvons tester les versions de production publiées en remplaçant temporairement notre clé.
🔗 Ressources » Obtenez le code complet de notre application de démonstration depuis notre dépôt GitHub compagnon.
Et voilà !
Les traductions over-the-air de Phrase peuvent vous faire gagner un temps précieux en publiant de nouvelles traductions directement auprès des utilisateurs de vos applications. Avec OTA, nous avons également l’avantage supplémentaire de sauter l’approbation de l’App Store (juste pour une mise à jour de texte). Et Phrase offre bien plus que la technologie OTA : nombreux formats de fichiers de traduction pris en charge, traduction automatique, synchronisation avec GitHub, GitLab et Bitbucket, branchement et versionnage, etc. Phrase permet de rationaliser le processus de localisation de votre application de bout en bout. Découvrez toutes les fonctionnalités de Phrase et inscrivez-vous pour un essai gratuit de 14 jours.


