Ionic : Traduire son application avec Angular-Translate

Angular-Translate

Il est souvent très pénible de rendre traduisible un projet déjà développé, même en ayant isolé chacune de ses vues et le texte qu'elles contiennent. C'est pourquoi j'ai pris pour habitude, en démarrant un projet de moyen/long terme, d'intégrer directement la gestion des Langues et traductions.
En effet, certaines applications n'auront JAMAIS vocation à être traduites. Cependant, si le moindre doute subsiste, je préfère ne pas prendre de risque, tant le "coût" de la maintenance est faible, face à la traduction d'une application déjà développée. Cela facilite aussi la tâche des correcteurs et des traducteurs qui peuvent travailler directement avec les fichiers de traduction.

Pour les applications Ionic et plus largement AngularJS, j'utilise Angular-Translate.


Installation

Dans votre terminal favoris, au niveau de votre projet Ionic, utilisez bower :

$ bower install angular-translate --save

Ajoutez angular-translate aux scripts chargés par votre fichier principal (par défaut, index.html):

<script src="lib/angular-translate/angular-translate.min.js"></script>  

Puis injectez le module angular pascalprecht.translate dans votre module principal (module "ionic" par défaut) :

angular.module('ionic', ['pascalprecht.translate']);  

En ce qui me concerne, je préfère dédier un module spécifique à cette tâche (l'internationalisation) pour découpler la mise en œuvre d'angular-translate du code de mon application.


Configuration

Les traductions sont construites comme des objets JSON :

{
    "HOME_PAGE_LINK_WELCOME": "Accéder à l'application"
}

Injectez $translateProvider dans le bloc de configuration de votre module puis procédez comme suit :

var translations = {  
    HOME_PAGE_LINK_WELCOME: 'Accéder à l'application'
};

$translateProvider
    .translations('fr', translations)
    .preferredLanguage('fr');

C'est tout pour la configuration "basique".

Plutôt que de fixer "en dur" la langue à utiliser (ici, 'fr'), on peut utiliser la méthode determinePreferredLanguage, laissant Angular-translate choisir la langue à utiliser par défaut. Le module s'appuie (dans l'ordre) sur les valeurs suivantes :

  • navigator.languages[0]
  • navigator.language
  • navigator.browserLanguage
  • navigator.systemLanguage
  • navigator.userLanguage

Si votre application Ionic doit aussi fonctionner sur de vieilles versions des navigateurs mobiles (et donc, sans Crosswalk), je vous invite à faire preuve de prudence car la présence d'une valeur valide n'est pas assurée.


Utilisation

Angular-Translate: vue d'ensemble

Dans vos templates, vous pouvez choisir entre le filtre et la directive translate.

<!-- directive -->  
<a href="#" translate>HOME_PAGE_LINK_WELCOME</a>

<!-- filtre -->  
<a href="#">{{ 'HOME_PAGE_LINK_WELCOME' | translate }}</a>  

L'utilisation de la directive est à préférer car chaque filtre entraîne la création d'un watcher (ou watch listener), dégradant ainsi les performances de l'application.

Comme vous pouvez le constater sur le schéma ci-dessus, la directive translate utilise le filtre translate qui dépend du service $translate. Dans vos contrôleurs, c'est le service ($translate) qui vous permettra d'obtenir une traduction.

$translate('HOME_PAGE_LINK_WELCOME').then(function(trad) {
    console.log(trad);
});

Ce dernier repose aussi sur un composant prenant en charge les traductions manquantes : quand une clé demandée ne peut être trouvée, par défaut, elle sera affichée telle quelle. Vous pouvez redéfinir ce comportement en utilisant votre propre "missing translation handler".
Si une clé risque de manquer à vos traductions anglaises, mais pas françaises (par exemple), vous pouvez demander au $translateProvider d'utiliser un "fallback language" (langue de rechange) avec la méthode fallbackLanguage :

$translateProvider.fallbackLanguage('fr');

Aller plus loin


1. Des traductions mieux organisées

Angular-translate permet de "compartimenter" nos traductions en utilisant des espaces de nom (ou namespaces :) !) :

{
    "HOME": {
        "WELCOME": "Accéder à l'application"
    }
}

On utilise ainsi la clé HOME.WELCOME pour accéder à notre traduction.

Pour certaines chaînes redondantes comme "Suivant", "Annuler", "Retour", il est possible de "pointer" vers une traduction existante :

{
    "HOME": {
        "WELCOME": "Accéder à l'application"
        "NEXT": "Suivant"
    },
    "DASHBOARD": {
        "NEXT": "@:HOME.NEXT"
    }
}

Je recommande d'utiliser cette fonctionnalité avec beaucoup de précautions : il est très simple d'oublier que la ressource que l'on s'apprête à modifier est affichée sur plusieurs vues différentes et de "casser" leur rendu, selon que le texte soit affiché dans une barre de titre, un bouton ou un onglet. Pour gagner en tranquillité, déplacez éventuellement toutes les ressources "répétées" dans un namespace dédié (par exemple, COMMON).

2. Traductions intégrant des variables

Angular-translate permet l'interpolation de valeur à l'intérieur de vos traductions :

{
    "HOME": {
        "WELCOME": "Bienvenue, {{username}}"
    }
}

Avec le service :

$translate('HOME.WELCOME', { username: 'Vince' });

Avec le filtre :

{{ 'HOME.WELCOME' | translate="{ username: 'Vince' }" }}

Avec la directive :

<a  href="#"  
    translate-value="{ username: 'Vince' }"
    translate>
    HOME.WELCOME
</a>  

3. Importer les traductions

Vous pouvez utiliser différents loader pour le chargement des traductions. Dans mes applications, j'ai pour habitude de créer un dossier "locales" dédié au stockage de ces traductions (au format JSON) et d'utiliser le loader "staticFilesLoader".

Installation avec bower :

$ bower install angular-translate-loader-static-files --save

Chargement dans le fichier principal :

<script src="lib/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js"></script>  

On l'exploite ensuite en informant le $translateProvider de son utilisation:

$translateProvider
    .useStaticFilesLoader({
        'prefix': 'locales/',
        'suffix': '.json'
    })
    .registerAvailableLanguageKeys(['en','fr'], {
        'en': 'en',
        'en_*': 'en',
        'fr': 'fr',
        'fr_*': 'fr',
        '*': 'fr'
    })
    // suite de la configuration...

prefix est le chemin relatif (depuis www) vers le dossier contenant les fichiers de traduction. On informe le loader de l'existence des fichiers fr.json et en.json grâce à la méthode registerAvailableLanguageKeys.

Les langues dont le code commence par en_ (en_GB, en_US...) seront traduites avec le fichier en.json, toutes les autres avec le fichier fr.json. determinePreferredLanguage enregistre une valeur type 'fr_FR' ou 'en_US', mais dans le cas où vous utiliseriez preferredLanguage, fixez les clés et fichiers correspondants.

en.json:

{
    "HOME": {
        "WELCOME": "Welcome, {{username}}"
    }
}

fr.json

{
    "HOME": {
        "WELCOME": "Bienvenue, {{username}}"
    }
}

NOTE : Dès lors que l'on utilise le staticFilesLoader, il est recommandé d'attendre que le statut de la plateforme (Cordova) soit ready avant de tenter de traduire une ressource.

Une fois les fichiers chargés, on peut utiliser la méthode instant du service $translate, plutôt que d'utiliser les Promesses.

var trad = $translate.instant('HOME.WELCOME');  

4. Changer de langue dynamiquement

Le service $translate offre une méthose use à laquelle vous devez passer la langue à utiliser :

$translate.use('en');

Angular-translate-once


Projet tiers, développé par Atticus White, qui répond à un problème spécifique que rencontrent les utilisateurs du staticFilesLoader désireux d'utiliser (aussi) le "One-time binding" d'AngularJS. Voir #1018 sur GitHub.

Installation avec bower :

$ bower install angular-translate-once --save

Chargement dans le fichier principal :

<script src="lib/angular-translate-once/src/translate-once.js"></script>  

Le module se chargeant dans le même namespace qu'angular-translate, aucune configuration spécifique n'est nécessaire. On peut remplacer toutes les utilisations de la directive translate par translate-once (qui tient aussi compte des interpolations fournies par translate-values).
Pour les attributs tels que placeholder (éléments input, textarea), pour lesquels on utilise généralement le filtre translate, Angular-translate-once fournit une directive translate-once-placeholder (mais aussi -value, -title et -alt).

Si je n'ai pas fait de mesures/benchmarks, j'ai par contre remarqué que pour plusieurs de mes vues "victimes" de nombreux cycles de $digest et utilisant le filtre translate, certaines fonctions de mon contrôleur passaient de 5, 10, 20 exécutions à ... une seule (c'est bien le but, nous sommes d'accord !).