asp.NET étape par étape - Tome 5 bis: Internationalisation d'une application asp.NET - Eviter de recompiler les fichiers ressources
Date de publication : 18/11/2005 , Date de mise à jour : 18/11/2005
I. Présentation
II. Classe permettant de contourner le problème lié aux fichiers .resources
III. Description du fichier XML contenant les traductions
IV. Code source de la classe
V. Conclusion
I. Présentation
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 ressources. 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é.
II. 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;
|
| 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 ressource, 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");
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 ressources.
Cet exemple n'utilise que deux fichiers ressources. Cependant, il serait nécessaire d'effectuer quelques modifications afin de générer autant de fichiers ressources 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 ressources | 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 ressources. 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("&", "&");
sw.Write(buffer);
sw.Close();
sr.Close();
} |
| Globalization : Thread de detection 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 utilise 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"); |
III. 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 ".
A 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'a pas pu etre chargée</FR>
<EN></EN>
</data>
<data name="Books_Add.ExceptionInvalidURL">
<FR>La liste des catégories n'a pas pu etre 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ésoud 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> |
IV. Code source de la classe
V. 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 ressources 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 mis à jour lors de la mise à jour de la classe.
 
|