Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Silverlight 4 : Localiser une application

[new:19/06/2010] L’informatique suit le monde, et le monde se mondialise… Une entreprise française devient une filiale d’un fond de pension américain, une éditeur de logiciel de Toulouse fusionne avec concurrent catalan, etc.. Les outils eux-mêmes se globalisent, prenez Phone 7 ou le prochain Eee Pad d’Asus sous Windows 7, pensez au Market Place centralisé de Microsoft pour vendre vos applications. Les applications ne peuvent plus se contenter d’être purement locales, elles doivent intégrer dès leur création les mécanismes permettant de les localiser. Le Framework lui-même, à la différence des plateformes précédentes, gère la localisation de façon naturelle et intégrée. Nous allons voir dans ce billet comment rendre vos applications Silverlight utilisables dans le monde entier.

Localiser

Pourquoi les informaticiens disent-ils “localiser”, anglicisme puisé de “localize”, plutôt que dire simplement “traduire” une application ? Encore un snobisme propre à nos profession ?

Non. Bien sûr que non. La traduction est à la localisation ce que l’algèbre est à la physique : un simple ingrédient permettant de créer le résultat final qui dépend de nombreux autres facteurs ! En effet, la “localisation” ne consiste pas seulement à traduire des bouts de texte, mais aussi à gérer ce qui est afférent à la culture de l’utilisateur.

Respecter la culture

Déjà traduire est une tâche difficile. Il y a le contexte, la mode, certains mots “valables” ne s’emploient plus ou plus que dans certaines situations. Traduire une application est un job difficile qui demande une parfaite maîtrise de la culture cible et une totale compréhension du texte qu’on traduit (donc dans notre cas du logiciel dont il fait partie).

Mais le respect de la culture cible va bien au-delà des mots : il faut prendre aussi en compte le format des dates, les civilités, l’ordre des mots, le format des numéros de téléphone, de sécurité sociale, la monnaie, les systèmes de mesure (S.I. ou non), les tailles habituelles du papier pour la mise en page par exemple (un format Letter américain n’a rien à voir avec de l’A4 traditionnel utilisé en France), voire même les couleurs, les connotations de certains gestes (s’il y a des photos, des vidéos, les italiens par exemple disent au revoir avec un petit geste de la main qui donne une impression très efféminée en France, faudra-t-il tourner une version spéciale de chaque vidéo intégrée dans le logiciel ou éviter d’être trop “typé” dès le départ ?)…

On le voit ici, “localiser” c’est beaucoup plus que “traduire” qui, de fait, n’est qu’un élément du processus de localisation qui englobe des réflexions qui dépassent de loin le remplacement de “oui” par “yes” ou “si” !

La culture sous .NET

Heureusement pour le développeur, la plateforme .NET prend en charge de base de nombreux aspects liés à la culture, encore faut-il utiliser convenablement ce socle et construire son logiciel par dessus et non pas à côté en réinventant la poudre !

Sous .NET les cultures sont représentées par des objets qui contiennent déjà des informations essentielles comme le formatage des nombres, le symbole monétaire, et d’autres informations qui ne s’inventent pas. Néanmoins il reste une part du travail à fournir : intégrer la localisation dans les mécanismes de l’application, traduire les textes, adapter les comportements du logiciel.

Les cultures sont codifiée sous la forme “langue-région”, par exemple le français parlé en France se code “fr-FR”, mais le français de nos amis québécois se code “fr-CA” alors que celui de nos voisins belges se note “fr-BE”. Il existe aussi un “fr-CH” pour les chuisses, et un “fr-LU” pour le luxe bourgeois des luxembourgeois.

On ne compte pas non plus les déclinaisons de l’anglais, “en-US” pour les GI de l’oncle SAM mais “en-GB” pour les grands bretons.

Ceux-là sont malgré tout facile à retenir, mais si je vous demande le code de l’ouzbek, ça se complique un peu !

Comme vous le constater, la langue est représentée par un code en minuscules, alors que la région (souvent un pays) est noté en majuscules.

.NET sait aussi comprend les codes simples, c’est à dire uniquement formés d’un code langue comme “fr” ou “us” sans précision de la région. Cela est beaucoup moins précis mais peut s’utiliser. On peut supposer un logiciel français qui n’a pas besoin de tenir compte des différences entre français de France et français suisse ou wallon. Mais cette pratique est déconseillée. Il suffira que le même logiciel nécessite un jour d’écrire une valeur monétaire pour que toute la localisation soit à revoir (les suisses risquent de faire attaque s’ils voient des prix en francs suisse affichés avec le symbole de l’euro !).

Bref, on code toujours ensemble langue et culture pour former un code de localisation. Plus techniquement il s’agit d’associer un code ISO 639 en minuscules pour la langue et un code ISO 3166 en majuscules pour la culture associée à une région ou un pays.

Pour aller plus loin, c’est ISO 639-1 qui est utilisé pour la langue. Il existe des extensions utilisant plus de lettres (dont le prochain ISO 639-5) mais qui ne sont pas utilisées sous .NET à ma connaissance. Il en va de même pour l’ISO 3166-1 et ses extensions. (Les liens proposés vers Wikipédia vous permettront d’obtenir les listes complètes).

Localiser une application

Une fois ces considérations générales abordées, voyons concrètement comme localiser une application Silverlight. Pour la démo l’application sera rudimentaire, un simple affichage avec quelques informations. Le processus que nous allons voir est indépendant de la complexité de l’application.

Le dossier des ressources

Le principal élément de la solution aux problèmes de localisation passent par la gestion de ressources quelle que soit la plateforme. Le Framework .NET n’y échappe pas. Notre application non plus et dans un premier temps nous allons ajouter un répertoire que nous appellerons “Resources” (j’ai l’habitude de faire tout en anglais par défaut, vous pouvez choisir un autre nom si cela vous chante).

Les ressources par défaut

A l’intérieur de ce dossier de ressources nous allons créer un fichier Resource qui contiendra les valeurs de la langue par défaut de l’application. Nommons-le “Strings.resx”.

Ajoutons une première chaîne “AppName” qui contiendra le nom de l’application.

image

Bien entendu nous utiliserons le data binding pour lier l’interface aux ressources. Pour ce faire nous ajoutons la déclaration d’un espace de noms dans le fichier Xaml du UserControl (appelons ce namespace “local”) :

   1: xmlns:local= "clr-namespace:SLLocalization.Resources"

Puis nous devons déclarer une ressource locale dans le UserControl :

   1: <UserControl.Resources>
   2:        <local:Strings x:Key="Strings" />
   3: </UserControl.Resources>

Maintenant nous pouvons ajouter un TextBlock en haut de notre page pour afficher le titre de l’application en nous liant à la ressource “Strings” :

   1: <TextBlock Text="{Binding AppName, Source={StaticResource Strings}}" />

J’ai bien entendu retiré de la balise ci-dessus tout ce qui concerne la mise en forme de l’objet texte (fonte, placement…).

Le résultat est immédiatement visible (une construction du projet s’avère nécessaire) :

image

Pour se rendre compte du résultat, le mieux est toujours d’effectuer un run. Et là que se passe-t-il ? Bug !

image

Comment ça “aucun constructeur” pour le type Strings ? Regardez bien la première capture, plus haut, lorsque le fichier de ressources Strings a été modifié pour fixer le nom de l’application. Et oui, nous avons bien spécifié pour “Access Modifier” la valeur “Public”.

Hélas, si vous regarder dans le code généré par Visual Studio, ici “Strings.Designer.cs” et que vous cherchez la déclaration du type Strings, vous tomberez sur un constructeur marqué “internal” !

Il s’agit d’un bug connu. Mais bien embêtant.

Vous avez toujours la possibilité de modifier “internal” en “public” et de relancer l’application, cela va fonctionner. Mais à chaque modification du fichier des ressources il faudra recommencer puisque le code source lié à la ressource est régénérer par Visual Studio automatiquement.

Contourner le bug

Avant d’aller plus loin dans notre quête de localisation il nous faut faire une pause et se demander comment nous pouvons contourner ce bug de façon élégante.

La façon la plus simple consiste à créer une classe publique qui elle exposera la ressource. Comme cette dernière possède un constructeur “internal” et que notre classe appartiendra bien au même namespace, nous n’aurons aucune difficulté à mettre en place cette “feinte”.

Le code de la classe “ResourceLocalizer” (localisateur de ressources) est le suivant :

   1: public class ResourceLocalizer
   2:    {
   3:        private readonly Resources.Strings appStrings = new Strings();
   4:        public Resources.Strings AppStrings
   5:        {
   6:            get { return appStrings; }
   7:        }
   8:    }

Mais ce n’est pas la seule modification à effectuer.

En effet, nous devons corriger la déclaration du namespace dans le fichier Xaml de la page principale, il devient :

   1: xmlns:local= "clr-namespace:SLLocalization"

De même, la déclaration de la ressource dans le UserControl devient :

   1: <UserControl.Resources>
   2:    <local:ResourceLocalizer x:Key="Strings" />
   3: </UserControl.Resources>

Et enfin, le binding du TextBlock se transforme un peu pour devenir :

   1: <TextBlock 
   2:   Text="{Binding AppStrings.AppName, Source={StaticResource Strings}}" />

Cette fois-ci, tout fonctionne à merveille, aussi bien au design (à condition d’avoir fait un build du projet) qu’au runtime où l’infamante exception a disparu !

Nous pouvons continuer le travail…

Indiquer les langues supportées

Aussi bizarre que cela puisse paraître et alors que toutes les attentions ont été portées dans le Framework pour supporter la localisation et alors même que VS 2010 est le plus complet des EDI qui soit, et bien nous tombons encore sur un os… Après le bug que nous venons de contourner, voici maintenant qu’aucune option n’a été prévue pour indiquer les langues supportées dans la page des propriétés du projet !

Pour arriver à nos fins, il va falloir encore une fois ruser.

  • Clic droit sur le nom du projet Silverlight
  • Puis choisir '”unload project” (décharger le projet)
  • Nouveau clic droit sur le projet
  • Choisir “Edit <nom du projet>” (modifier le projet)

Dans le nœud “PropertyGroup” de ce fichier XML qui décrit le projet, ajoutons la ligne suivante (par exemple à la fin de la liste, juste avant la fermeture de la balise PropertyGroup) :

   1: <SupportedCultures>fr-FR;en-US</SupportedCultures>

SupportedCultures n’apparaît pas dans IntelliSense, encore un petit problème. Mais ce n’est pas grave. Dans cette nouvelle balise il suffit d’indiquer les couples langue-culture qui sont supportés par l’application en les séparant d’un point virgule.

Comme vous le notez, j’ai placez deux langues dans la balise : le français de France (fr-FR) et l’anglais américain (en-US) car tout ce travail n’a de sens que si nous supportons au moins deux langues !

On n’oublie pas de sauvegarder le fichier projet, de fermer sa fenêtre et de refaire un clic droit sur le projet puis “reload project” (recharger le projet).

Nous pouvons à nouveau travailler sur notre application et lui ajouter enfin une nouvelle langue !

Ajouter le support de la nouvelle langue

Le décor étant en place, il ne reste plus qu’à ajouter une nouvelle langue. Pour ce faire nous allons créer dans le répertoire des ressources un nouveau fichier ressources qui s’appellera “Strings.en-US.resx”, c’est à dire de la même façon que le fichier que nous avons déjà créé mais en ajoutant la culture comme extension.

Le fichier de ressources doit contenir les mêmes identificateurs que le fichier d’origine, par exemple nous devons recréer une chaîne ayant pour clé “AppName” :

image

On remarquera aussi que “Access Modifier” est placé à la valeur “No code generation” (pas de génération de code). De fait aucun fichier “.cs” ne se trouve associé.

Quelques chaines de plus

En réalité une seule suffira bien pour cet exemple. Ici nous allons faire intervenir d’autres éléments de la localisation notamment le format des dates et le nom des jours et des mois. De plus, comme nous avons déjà montrer comment accéder aux éléments localisés sous Xaml via binding, nous allons voir comment bénéficier des mêmes services par code.

La chaîne que nous allons créer a pour clé “Today”. Elle permettra d’indiquer la date et l’heure.

Dans Strings.resx (le fichier par défaut) nous déclarons la chaîne comme suit :

   1: Nous sommes le {0} et il est {1}

Vous remarquez l’utilisation des marqueurs propres à “string.Format” qui permettent justement de modifier le placement d’éléments variables.

Dans le fichier Strings.en-US.resx la chaîne sera définie de la façon suivante :

   1: We are {0} and local time is {1}

Dans le code de la page principale (MainPage.xaml.cs) nous utilisons cette ressource pour fixer la valeur d’un TextBlock appelé “txtDate” :

   1: void MainPage_Loaded(object sender, RoutedEventArgs e)
   2: {
   3:   txtDate.Text = string.Format(SLLocalization.Resources.Strings.Today,
   4:            DateTime.Now.ToLongDateString(),
   5:            DateTime.Now.ToShortTimeString());
   6:  }

Cette initialisation s’effectue dans le gestionnaire Loaded du UserControl définissant la page.

Ce qui est intéressant ici est de voir que nous ne passons plus par la classe localisatrice de ressources nécessaire en Xaml (pour contourner le bug de VS 2010).

Nous accédons directement à la propriété Today, qui est statique par défaut, de la classe Strings (la classe porte le nom du fichier, par défaut aussi), du namespace Resources (un sous répertoire de projet est fournisseur de namespace par défaut) de l’application SLLocalization.

Pour fixer le texte nous utilisons string.Format à qui nous passons d’une part la chaîne localisée et en arguments la date en format long et l’heure en format court.

Prise en charge de la langue en cours

Si l’on ne fait rien de plus, le projet tournera en français et devrait tourner en anglais sur un Windows anglais. Mais j’avoue ici une lacune, je n’ai pas réussi à savoir si cela était réellement automatique. N’ayant pas de machine totalement en US sous la main, je vais creuser la question ce qui fera l’objet d’un petit billet dans les jours à venir…

Pour tester le passage en anglais sur ma machine (et cela serait le cas chez vous aussi, que cela soit pour l’anglais, l’espagnol ou autre d’ailleurs) il faut forcer un peu les choses.

Forçage par balise du plugin

Il est possible de forcer la prise en charge d’une culture au niveau du plugin, c’est à dire de la balise de définition de ce dernier, soit dans la page HTML hôte soit dans une page ASP.NET.

Les paramètres concernés s’appellent en toute logique :

  • culture
  • uiculture

Leur valeur doit être généralement la même, il s’agit du code culture complet, “fr-FR” par exemple.

De cette façon on s’assure que l’application sera toujours dans une langue donnée quel que soit le browser et la machine hôte.

Cela peut être utile dans certains cas certainement. Mais je ne vais pas utiliser cette méthode ici.

Forçage par code

Il suffit de forcer la culture dans App.xaml.cs au niveau du constructeur  “Public App()” et de préférence avant l’appel à InitializeComponent();

Le forçage s’effectue assez classiquement par le biais du thread courant :

   1: Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
   2: Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");

Vérification

Un run avec le code ci-dessus donnera l’affichage suivant :

image

On notera le titre traduit (en blanc sur fond bleu, au-dessus il s’agit de l’onglet de IE) ainsi que le respect de la culture américaine dans le format de la date, le nom du jour, du mois et l’indication de l’heure en AM/PM.

Si nous commentons les deux lignes de code fixant la culture un nouveau run nous donnera ceci :

 image C’est pas magnifique non ? (oui, il est bien 2:10 du matin ce n’est pas un nouveau bug !).

Plus loin que la traduction

Comme je le disais en introduction, la localisation va bien plus loin que la simple traduction des chaînes de caractères.

La technique exposée ici permet de gérer tous les autres cas de figure. Rappelez-vous qu’un fichier de ressources peut contenir des chaînes mais aussi des images, des icones, de l’audio, etc. Si on désire avoir une image de fond avec le drapeau français pour la version française et un drapeau américain pour la version anglaise, il suffira de définir une clé “ImageDeFond” qui dans un fichier sera un drapeau français et dans l’autre sera un drapeau américain…

De même on peut vouloir définir de la même façon un jeu de couleur, des indicateurs booléens, tout ce qui peut servir à personnaliser l’application selon la culture de l’utilisateur.

Certaines informations, comme par exemple les chaînes, pourront être utilisées directement par binding dans les pages. D’autres peuvent simplement servir par code à formater des données comme nous l’avons vu ici.

Avec des fichiers Wav on pourra proposer des informations audio traduites elles aussi par le même mécanisme (reste à trouver les traducteurs et surtout les speakers ayant le bon accent pour faire les prises de son !).

Conclusion

Localiser une application Silverlight n’est pas très compliqué mais il faut admettre que cela n’est pas aussi simple qu’on pourrait l’espérer surtout avec les quelques petits bugs que VS met en travers de notre route.

Mais grâce à quelques explications on s’en sort finalement sans trop de peine. Et puis c’est le mécanisme de base qui demande un peu de travail, ensuite il suffit de remplir les fichiers de ressources et d’utiliser le contenu. On a ainsi un développement qui reste simple mais un résultat “pro” d’une application localisée.

Good Localization et… Stay Tuned !

Le code complet du projet :

blog comments powered by Disqus