Traduction over-the-air Flutter avec Phrase

Arrêtez d’attendre l’approbation du magasin d’applications mobiles et obtenez du contenu frais pour vos utilisateurs d’applications Flutter instantanément à travers le monde—avec Phrase over-the-air.

Rien n’est aussi frustrant que d’avoir du contenu flambant neuf prêt à être diffusé dans votre application mobile, pour ensuite devoir attendre l’approbation de l’App Store ou du Play Store afin de le rendre accessible à vos utilisateurs (je m’adresse particulièrement à vous, Apple).
Avec Phrase Over-the-Air, vous pouvez intégrer des traductions fraîches dans votre application en direct d’un simple clic. Pas de version à publier, pas d’approbation de la boutique, pas de complication. 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 à la CLI de Phrase, puis à Phrase over-the-air (OTA). N’hésitez pas à passer directement à la partie de votre choix selon vos besoins.

Notre application de démonstration

Notre modeste banc d’essai s’appelle Héros de l’informatique. Nous avons d’abord présenté cette application dans Un Guide sur la localisation Flutter, qui est un tutoriel approfondi sur l’i18n de Flutter. Nous allons passer brièvement en revue l’application ici.

🔗 Ressources » Obtenez le code complet de notre application de démonstration sur GitHub.

Démo | PhraseVoici les Héros

Comme vous pouvez l’imaginer, l’application est assez simple :

.
└── lib/
    ├── main.dart (MaterialApp)
    └── features/
        └── heroes/
            ├── hero_list.dart (ListeHéros)
            └── 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(
      Titre : « Héros de l’informatique »,
      Thème : ThemeData(
        Palette principale : Colors.blue,
      ),
      Accueil : HeroList(title: 'Héros de l’informatique'),
    );
  }
}
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: Texte('6 Héros'),
            ),
            Développé (
              Enfant : Vue de liste(
                enfant : <Widget>[
                  HeroCard(
                    Nom : 'Grace Hopper',
                    born: '9 décembre 1906',
                    bio: A élaboré la théorie de la machine indépendante...
                    imagePath: 'assets/images/grace_hopper.jpg',
                  ),
                  HeroCard(
                    name: 'Alan Turing',
                    born: '23 juin 1912',
                    bio: 'Père de la science informatique théorique…',
                    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),
        Enfant : 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 ? '' : 'Né $born',
                      style: TextStyle(
                        fontSize: 12,
                        fontWeight: FontWeight.w300,
                      ),
                    ),
                  ),
                  Text(
                    bio ,
                    style: TextStyle(fontSize: 14),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

C’est essentiellement notre application en résumé. Le problème est que nos chaînes sont actuellement codées en dur, ce qui n’est pas bon pour la localisation. Internationalisez ce petit chiot.

🗒 Note » Si vous souhaitez coder avec nous à partir de ce point, cloner la branche de départ de notre dépôt compagnon.

Versions utilisées

Nous utilisons les versions suivantes de la langue, du framework et du package dans cet article :

  • 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 les widgets Material ou Cupertino.
  • intl 0.17.0 — la colonne vertébrale du système de localisationa; nous permet de créer et d’utiliser nos propres localisationsa; utilisé pour le formatage des dates et des nombresa; 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.

Localiser notre application Flutter

Ok, intégrons 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 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 notre pubspec.yaml pour installer les packages.

# ...
dépendances :
  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

✋🏽 Alerte » Le SDK Phrase Flutter, qui se connecte à Phrase OTA, nécessite la version 0.17.0 du package intl, alors 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 de plus à notre pubspec.yaml.

# ...
flutter:
  generate: true
# ...

🔗 Ressource » Si vous souhaitez en savoir plus sur le fonctionnement de 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
# Quelle traduction est la par défaut ou modèle
template-arb-file: app_en.arb
# Comment appeler les fichiers Dart générés
output-localization-file : app_localizations.dart

🔗 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.

Configurer notre application iOS

Une étape de plus est nécessaire pour iOS ici : si nous n’ajoutons pas nos paramètres régionaux pris en charge dans le fichier Info.plist d’iOS, les choses pourraient ne pas fonctionner comme prévu.

<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleLocalizations</key>
	<array>
		<chaîne>en</chaîne>
		<chaîne>ar</chaîne>
	</array>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
    <!-- ... -->

Nous prendrons en charge l’anglais et l’arabe pour notre démo. Bien sûr, vous pouvez ajouter les langues you que vous souhaitez prendre en charge ici.

Ajout de fichiers de traduction

Suivant, 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é/valeur JSON classiques. Nous copierons toutes les chaînes codées en dur dans notre application vers nos fichiers de traduction.

{
  "appTitle": « Héros de l'informatique »,
  // ...
  "hopperName": "Grace Hopper",
  "hopperBio": "A élaboré la théorie des langages de programmation indépendants de la machine.",
  "turingName": "Alan Turing",
  "turingBio": "Père de l'informatique théorique et de l'intelligence artificielle."
  // ...
}
{
  "appTitle" : "Héros des sciences informatiques",
  // ...
  "hopperName": "جريس هوبر",
  "hopperBio": "A conçu une théorie des langages de programmation indépendants de la machine.",
  "turingName": "آلان تورينج",
  "turingBio": "Le père de l’informatique théorique et de l’intelligence artificielle.",
  // ...
}

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.

🗒 Note » 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 de code

Avec nos traductions en place, il est temps de commencer la génération de code. Tout d’abord, configurons notre 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(
      Titre : « Héros de l'informatique »,
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en', ''),
        Locale('ar', ''),
      ],
      Thème : ThemeData(
        Palette principale : Couleurs.bleu,
      ),
      Accueil : HeroList(Titre: « Héros de l’informatique »)
    );
  }
}

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, cela a fonctionné!

Localiser l’application

D’accord, nettoyons notre main.dart et localisons-le. Nous allons retirer l’import direct de flutter_localizations et utiliser à la place notre AppLocalizations 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,
      Langues prises en charge : AppLocalizations.supportedLocales,
      Thème : ThemeData(
        Palette principale : Couleurs.bleu,
      ),
      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 Accueil. 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 lorsque 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.
Localisons nos HeroList et HeroCard widgets pendant que nous y sommes.

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(
        Titre : Text(title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        Enfant : Colonne (
          Enfants : [
            Padding(
              padding: const EdgeInsets.only(bottom: + 8,0),
              Enfant : Text(t.heroCount(6)),
            ),
            Développé (
              Enfant : ListView(
                children: <Widget>[
                  HeroCard(
                    name: t.hopperName,
                    né: 9 décembre 1906,
                    bio: t.hopperBio,
                    imagePath: 'assets/images/grace_hopper.jpg',
                  ),
                  HeroCard(
                    nom : t.turingName,
                    né: 23 juin 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(
      Enfant : Padding(
        padding: const EdgeInsets.all(4.0),
        Enfant : Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(
              // <Image déjà localisée depuis this.imagePath>...
            ),
            Développé (
              child: Colonne (
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Padding(
                    // <Nom déjà localisé depuis this.name>...
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 2, bottom: 4),
                    child: Texte(
                      born.isEmpty ? '' : t.heroBorn(bornDateTime),
                      Style : TextStyle(
                       fontSize: 12 ,
                       fontWeight: FontWeight.w300,
                      ),
                    ),
                  ),
                  // <Bio déjà localisé depuis this.bio>...
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

🔗 Ressource » Si vous vous demandez ce que fait l’appel t.heroCount(6), lisez tout à ce sujet dans notre Guide de la localisation de Flutter.

Maintenant, lorsque nous définissons les paramètres régionaux de notre système d’exploitation mobile sur l’arabe, nous pouvons voir nos chaînes traduites. Hourra 😃.

Application de démonstration traduite | PhraseNotre application, parfaitement mondialisée

Connexion à Phrase

Avec notre application localisée, passons à la vitesse supérieure en la connectant à Phrase.

🗒 Note » Si vous souhaitez coder avec nous à partir de ce point, cloner la branche localisée de notre dépôt GitHub compagnon.

Création d’un projet Phrase

🗒 Note » 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 à Projets ➞ Créer un nouveau projet.
Créer un nouveau projet dans Phrase | Phrase
Cela ouvrira la boîte de dialogue Ajouter un projet. 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 Enregistrer.
Fenêtre Ajouter un projet dans Phrase | Phrase
Suivant, nous allons accéder à la page de configuration du projet. Cliquons sur le bouton Set up languages pour ajouter les langues prises en charge par notre application.
Configurer de nouvelles langues dans Phrase | Phrase
Ajouter des langues d'application prises en charge | Phrase
Nous pouvons ajouter autant de langues que nous le souhaitons ici. La première sera la langue par défaut. Après avoir ajouté nos paramètres régionaux, cliquons sur Créer des langues. Nous serons ramenés à la page de configuration du projet. C’est toute la configuration dont nous avons besoin à ce stade, alors cliquons sur le bouton Passer la configuration pour continuer.
Passer la configuration | Phrase
À 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. Cliquer sur notre nom ouvre un menu déroulant avec une option Jetons d’accès, sur laquelle il faut bien sûr cliquer maintenant.
Jeton d'accès | Phrase
Génération d’un nouveau jeton | Phrase
Cela ouvre la page Jetons d’accès, révélant un bouton Générer un jeton. Cliquons sur ce bouton pour ouvrir la boîte de dialogue Générer un jeton.
Dialogue de génération de jeton | Phrase
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.
Jeton révélé | Phrase

✋🏽 Avis » 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, nous obtiendrons quelque chose comme ceci :
Configuration réussie du CLI | Phrase

🔗 Ressource » Si vous n’êtes pas sur macOS ou ne souhaitez pas utiliser Homebrew, consultez la documentation de Phrase CLI pour 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

Invite de jeton d'accès API | Phrase
Après avoir entré le jeton d’accès que nous avons généré plus tôt et cliqué sur Entrer, nous serons invités à sélectionner notre projet Phrase.
Sélectionner notre projet Phrase | 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 Entrée. Sinon, nous pouvons sélectionner 1. (arb) dans la liste.
Sélectionner le bon format de fichier | Phrase
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 locales à notre projet Phrase.
Formats de fichiers attendus dans Phrase | 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. Faites cela en saisissant y puis en appuyant sur Entrée.

🗒 Note » 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.

✋🏽 Attention » 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 à Langues et ouvrir une langue pour voir vos traductions prêtes à être modifiées.
Menu Phrase over-the-air | Phrase

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

Voici le résumé : après avoir connecté votre projet à Phrase OTA et déployé une nouvelle version de votre application avec OTA activé, ce qui suit se produit.

  1. Un utilisateur ouvre votre application, déclenchant une récupération OTA de la traduction en arrière-plan.
  2. 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 ».

Flux de travail over-the-air | Phrase
Ça a l’air génial ? Alors, qu’attendez-vous ? Connectez 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.

# ...
dépendances :
  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.
Message de succès | Phrase

✋🏽 Attention » 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.

🔗 Ressource » 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.
Menu over-the-air | Phrase
Création d'une distribution over-the-air | Phrase
Cela ouvrira la page Over the Air avec un joli bouton Créer une distribution. Cliquez sur le bouton Add distribution pour ouvrir la boîte de dialogue correspondante.
Boîte de dialogue Ajouter une distribution | Phrase
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.

🗒 Note » Vous ne pouvez pas modifier les Plateformes que votre distribution cible après l’avoir créée. Vous pouvez cependant modifier la distribution Paramètres à tout moment.

Cliquez sur Enregistrer lorsque vous êtes satisfait de votre configuration pour valider vos modifications et ouvrir la page des détails de la distribution.
Page de détail de la distribution | Phrase
Les trois clés, Identifiant de distribution, Secret de développement et Secret de production, sont essentielles pour connecter votre projet Flutter à cette distribution OTA, et vous les utiliserez dans un instant.

🗒 Note » Ne vous inquiétez pas : vous pouvez accéder aux clés de distribution à tout moment.

Créer une version de développement

Ok, maintenant que vous avez une distribution, vous aurez besoin d’une OTA release. 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 Projets ➞ <Votre Projet> ➞ Langues ➞ en, et mettez à jour la chaîne appTitle.
Mettre à jour la chaîne apptitle | Phrase
Maintenant, vous pouvez créer une version. Allez dans ➞ Over-the-Air et cliquez sur le nom de votre distribution.
Créer une version dans le menu des détails de distribution | Phrase
Le bouton Créer une version près du bas de la page ouvrira la boîte de dialogue Ajouter une version.
Fenêtre de dialogue Ajouter une version | Phrase
Pour simplifier, rendez votre 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 probablement judicieux d’ajouter une description pour la postérité. Une fois satisfait, cliquez sur Enregistrer 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.
Version non publiée | Phrase

✋🏽 Attention » 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

D’accord, 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 ,
      Thème : ThemeData(
        Palette principale : 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é.
Veuillez noter la console de débogage.
Message de débogage | Phrase<3>
Phrase n’a pas trouvé de cache de traductions OTA, ce qui est attendu. 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 attendu.
Appli démo | Phrase
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.
bouton de relance | Phrase

C’est un </1>redémarrage complet, pas un rechargement à chaud

Veuillez noter que la traduction mise à jour de appTitle est maintenant affichée.
Application de démonstration | Phrase
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.

D’accord, 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.

# ...
dépendances :
  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.
page des détails de la distribution sur la console Phrase | 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
## ...

✋🏽 Attention » 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 et kReleaseMode sont des constantes
  // intégré dans foundation.dart (importé ci-dessus)
  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.
Application de démonstration | Phrase
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.
Libérons cette bête. Dans la console Phrase, nous allons nous rendre à Over the Air et cliquer sur le nom de notre distribution pour ouvrir sa page de 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é.
Publication des champions de l'informatique | Phrase
Lorsque nous cliquons sur Publish,, nous obtiendrons une boîte de dialogue de confirmation ; confirmer donne un message indiquant que la version est publiée avec succès.

✋🏽 Alerte » 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 🏄.
Démo de l’application terminée | Phrase
Quelques notes :

  • 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é.

🔗 Ressource » Obtenez le code complet de notre application de démonstration depuis notre dépôt GitHub compagnon.

Au revoir pour l’instant

Les traductions over-the-air de Phrase peuvent nous faire gagner tellement de temps, car nous publions de nouvelles traductions directement auprès de nos utilisateurs de l’application. 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 OTA, y compris une multitude de formats de fichiers de traduction pris en charge ; traduction automatique ; synchronisation avec GitHub, GitLab et Bitbucket ; branchement et versionnage ; et bien plus encore. Phrase peut 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.

Posts associés

Software localization blog category featured image | Phrase

Blog post

Le guide ultime de la localisation JavaScript

Démarrez la localisation JavaScript de votre navigateur avec ce guide complet et préparez votre application pour les utilisateurs internationaux.

Software localization blog category featured image | Phrase

Blog post

Le guide ultime de la localisation Flutter

Décodons les secrets de la localisation Flutter afin que vous puissiez parler la langue de vos utilisateurs et continuer à coder votre chemin vers la domination mondiale.

Software localization blog category featured image | Phrase

Blog post

Comment utiliser le plug-in de localisation de Phrase Strings pour WordPress

Apprenez à traduire les pages WordPress ou encore des articles dans plusieurs langues avec l’intégration Phrase Strings pour WordPress.

Software localization blog category featured image | Phrase

Blog post

Détection des paramètres régionaux d’un utilisateur dans une application web

L’un des problèmes les plus courants dans le développement d’applications web est la détection des paramètres régionaux d’un utilisateur. Voici comment le faire correctement.

Software localization blog category featured image | Phrase

Blog post

Traduction de l’application iOS over-the-air avec des chaînes Phrase

Si vous voulez vous assurer que tous vos utilisateurs d’application obtiennent le bon texte, la fonctionnalité over-the-air de Phrase peut vous aider à publier vos mises à jour de traduction instantanément.