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

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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
Sélectionnez

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 diveres
Globalization : Création du manager
Sélectionnez
	
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
Sélectionnez
		
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 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
Sélectionnez

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
Sélectionnez

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 detection d'une mise à jour du fichier XML
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

<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
Sélectionnez

<root>
	<data name="Books_Add.ExceptionListingCategories">
		<FR>La liste des catégories n&#39;a pas pu etre chargée</FR>
		<EN></EN>
	</data>
	<data name="Books_Add.ExceptionInvalidURL">
		<FR>La liste des catégories n&#39;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
Sélectionnez

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

IV. Code source de la classe

Le code de la classe est disponible ici: Classe Globalization

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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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.