asp.NET étape par étape - Tome 5 bis : Internationalisation d'une application asp.NET - Éviter de recompiler les fichiers .resources

Didier Danse

Dans l'article consacré aux techniques d'internationalisation en .NET, il était dit que chaque technique avait ses inconvénients. Voici une manière de contourner le problème de l'utilisation des fichiers .resources. Il en existe certainement d'autres et la technique présentée dans cet article est simplement celle mise en place pour faciliter la manipulation des textes dans différents projets que j'ai réalisés. ♪

- Titre : asp.NET étape par étape - Tome 5 bis : Internationalisation d'une application asp.NET - Éviter de recompiler les fichiers .resources

- Auteur : Didier Danse

- Parution : 18 novembre 2005

- Mise à jour : 18 novembre 2005

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2005 Danse Didier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Classe permettant de contourner le problème lié aux fichiers .resources

- II. Description du fichier XML contenant les traductions

- III. Code source de la classe

- IV. Conclusion

I - Classe permettant de contourner le problème lié aux fichiers .resources

Afin de faciliter la mise à jour des différents textes de manière dynamique sans une recompilation (en cas de faute d'orthographe par exemple), nous utilisons notre propre classe reposant sur les différents principes cités dans l'article sur les techniques d'internationalisation en .NET. Ceci permet à une personne autre que les développeurs actuels, par exemple les administrateurs de l'application, de mettre à jour les différents textes. Ces textes se trouvent dans un fichier XML qui sera présenté dans le point suivant.

Une page d'administration des textes sert d'interface entre l'administrateur et le fichier XML.

Concrètement, cette classe permet de vérifier si le fichier XML a été mis à jour. Si c'est le cas, les fichiers .resources sont également mis à jour.

En voici les éléments les plus importants :

Classe Globalization

public class Globalization
{
static protected ResourceManager manager = null;
static private string resourceName = null;
static private DateTime lastModification = DateTime.Now;
static Thread ThreadcheckForUpdates;
static private string xmlName;
static private string path;

// Propriétés diverses

Globalization : Création du manager

static public string ResourcePath
{
set{ resourceName = value; manager = ResourceManager.CreateFileBasedResourceManager(value, path, null); }
}

Remarque: Afin d'être certain que le manager a bien été spécifié, on pourrait éviter de mettre la classe en static. Un constructeur avec comme paramètres les valeurs nécessaires (nom du fichier .resources, répertoire dans lequel celui-ci se trouve et nom du fichier XML) serait préférable.

Globalization : Récupération d'un texte

static public string GetString(string page, string component)
{
FileInfo fi = new FileInfo(path + resourceName + ".resources");
if (!fi.Exists)
GenerateResourceFiles();

if (manager == null)
throw new Exception("Xml File Url is undefined");

// Récupération de la langue

string returnString;
try { returnString = manager.GetString(page + "." + component); }
catch (Exception) { returnString = ""; }
manager.ReleaseAllResources();
return returnString;
}

Vu que les textes sont contenus dans un fichier XML, on utilise un parseur XML (DOM) pour les récupérer. On va ainsi boucler sur les différents nœuds pour en récupérer les valeurs correspondantes. Il est dès lors possible d'ajouter ces valeurs dans les fichiers .resources.

Cet exemple n'utilise que deux fichiers .resources. Cependant, il serait nécessaire d'effectuer quelques modifications afin de générer autant de fichiers .resources qu'il n'y a de nœuds au sein des éléments « data » et ce de manière automatique.

Globalization : Génération des fichiers .resources

static private void GenerateResourceFiles()
{
FileStream fs = new FileStream(path + resourceName + ".resources", FileMode.OpenOrCreate, FileAccess.Write);
FileStream fsEN = new FileStream(path + resourceName + ".EN.resources", FileMode.OpenOrCreate, FileAccess.Write);
IResourceWriter writer = new ResourceWriter(fs);
IResourceWriter writerEN = new ResourceWriter(fsEN);

FileInfo fi = new FileInfo(path + "_" + xmlName);
if (!fi.Exists)
XMLTransform();

XmlDocument document = new XmlDocument();
document.Load(path + "_" + xmlName);

XmlNode rootnode = document.FirstChild;
foreach (XmlNode datanode in rootnode)
foreach (XmlNode nodeLanguage in datanode)
if (nodeLanguage.Name == "FR")
writer.AddResource(datanode.Attributes["name"].Value, nodeLanguage.InnerText);
else
writerEN.AddResource(datanode.Attributes["name"].Value, nodeLanguage.InnerText);

writer.Generate();
writer.Close();

writerEN.Generate();
writerEN.Close();
}

Sauf si un codage particulier a été spécifié, un fichier XML ne doit comporter ni des caractères accentués ni des caractères spéciaux tels que « & ». La transformation de ces caractères serait trop fastidieuse pour l'utilisateur qui modifierait les textes, c'est pourquoi il est nécessaire de transformer automatiquement ceux-ci sans qu'il ne s'en rende compte. La transformation effectue une lecture dans le fichier XML de base et remplace les caractères qui le nécessitent avant d'écrire dans un autre fichier le contenu modifié. C'est ce fichier qui sera réellement utilisé pour la génération des fichiers .resources. Cette version ne permet cependant pas de remplacer les « < », « > » et « ? ». En utilisant le parseur DOM, le remplacement est envisageable.

Globalization : Suppression des caractères invalides

static private void XMLTransform()
{
StreamReader sr = new StreamReader(path + xmlName, System.Text.Encoding.Default);
StreamWriter sw = new StreamWriter(path + "_" + xmlName, false, System.Text.Encoding.UTF8);

string buffer = sr.ReadToEnd();

buffer = buffer.Replace("&", "&amp;");
// Autres remplacements de caractères

sw.Write(buffer);

sw.Close();
sr.Close();
}

Globalization : Thread de détection d'une mise à jour du fichier XML

static private void CheckForUpdatesThread()
{
while (true)
{
FileInfo fi = new FileInfo(path + xmlName);
if (fi.Exists && fi.LastWriteTime != lastModification)
{
Globalization.GenerateResourceFiles();
lastModification = fi.LastWriteTime;
}
Thread.Sleep(60000);    // 1 minute
}
}

Dans le Global.asax, il est alors nécessaire d'appeler cette classe.

Extrait du Global.asax

protected void Application_Start(Object sender, EventArgs e)
{
Globalization.Path = Server.MapPath(".") + "\\";
Globalization.ResourcePath = "items";
Globalization.XmlPath = "globalization.xml";

Globalization.CheckForUpdates();
}

Pour récupérer une valeur provenant de ce fichier .resources, rien de plus simple, il suffit de passer le nom du composant (si les conventions ont été respectées), le nom de la page étant passé implicitement :

Récupération d'une valeur provenant du fichier .resources en utilisant la classe précitée

LaText.Text = Globalization.Globalization.GetString("LaText");

ou, si l'on a utilisé une autre convention,

Récupération d'une valeur provenant du fichier .resources en utilisant la classe précitée

LaText.Text = Globalization.Globalization.GetString("NomUserControl", "LaText");

II - Description du fichier XML contenant les traductions

Le fichier XML est très simple. Chaque nœud, excepté le nom père, contient un attribut name. Cet attribut est le nom de l'élément. Pour rappel, la convention choisie est « Nomdelapage.NomControle ».

À chacun de ces nœuds sont attachés des nœuds fils qui ont pour nom les initiales de la langue.

Fichier XML contenant les traductions

<root>
<data name="Books_Add.ExceptionListingCategories">
<FR>La liste des catégories n'a pas pu être chargée</FR>
<EN></EN>
</data>
<data name="Books_Add.ExceptionInvalidURL">
<FR>La liste des catégories n'a pas pu être chargée</FR>
<EN></EN>
</data>
</root>

Le fichier, après transformation, ne contient plus de caractères accentués, mais bien les codes ASCII correspondants.

Fichier XML après transformation

<root>
<data name="Books_Add.ExceptionListingCategories">
<FR>La liste des catégories n&#39;a pas pu être chargée</FR>
<EN></EN>
</data>
<data name="Books_Add.ExceptionInvalidURL">
<FR>La liste des catégories n&#39;a pas pu être chargée</FR>
<EN></EN>
</data>
</root>

L'utilisation de ce fichier pour y stocker de plus grandes quantités d'informations avec, entre autres, des balises de mise en forme, est tout aussi simple. La norme XML résout tous les problèmes qui pourraient survenir suite à l'insertion d'éléments HTML qui pourraient interférer dans le fichier XML et le rendre mal formé grâce à la notion de CDATA. Le contenu de cette « directive » est simplement lu et non pas interprété par le parseur.

Fichier XML contenant des balises HTML

<root>
<data name="FavoriteBooks.Title">
<FR>
<![CDATA[
Livres
<br/>
Suite…
]]>
</FR>
<EN>Books</EN>
</data>
</root>

III - Code source de la classe

Le code de la classe est disponible ici : Classe Globalization.

IV - Conclusion

Je l'ai dit et je le répète, ce n'est pas LA solution, mais bien une solution. Cette classe évoluera en fonction des besoins, mais les étapes certaines sont :

- gestion plus de deux langues (nombre illimité, possibilité de manipuler plusieurs fichiers .resources simultanément lors du parsing des informations à ajouter) ;

- Custom Control d'administration (c'est-à-dire qui peut s'ajouter dans la toolbox de Visual Studio.NET, le détail de celui-ci fera l'objet d'un tutoriel complet) ;

- améliorer l'interface d'administration (mise online prochainement) ;

- …

J'espère en tout cas que cet article vous donnera des idées pour compléter cette solution (n'hésitez pas à me les envoyer) ou pour développer votre propre solution qui répondra mieux à vos besoins.

Cet article sera modifié lors de la mise à jour de la classe.