none
Globalisation par utilisation de resx non embarqués sous forme de ressource RRS feed

  • Question

  • Dans une application multi langue nous utilisons une bibliothèque de classe contenant des ressources de traduction pour différentes cultures : neutre, us, gb, de ...
    Jusqu'à présent ces ressources étaient embarquées dans l'application et nous utilisions la classe générée depuis visual studio pour accéder à ces ressources : le modificateur de visibilité étant défini sur publique les ressources sont visibles depuis les autres assemblies par un simple :

    Ressources.MyResource.MyChaine;

    Désormais nous devons permettre à nos clients de modifier si besoin ces ressources ou d'ajouter de nouvelles langues avant de lancer l'application. Nous ne pouvons donc plus embarquer ces resources sous forme d'assembly satellites dans notre projet comme ceci était le cas avant.

    Nous cherchons donc le meilleur moyen de pouvoir utiliser nos fichiers resx ( sous forme xml ) pour continuer à gérer la traduction en se basant sur la culture du thread courant : Thread.CurrentThread.CurrentUICulture

    Nous avons regarder du coté des classes et méthodes : ResXResourceReader, CreateFileBasedResourceManager mais sans trouver de solution vraiment satisfaisante pour le moment.

    Nous serions donc intéressés par de nouveaux avis, liens ou retours sur ce genre de problème.

    Cordialement
    mardi 12 janvier 2010 16:16
    Modérateur

Réponses

  • Bonjour,
    Voici la solution que je propose:

    1- Generer une un fichier ".cs" et un fichier binaire ".resource" à partir du ficher ".resx" avec l'outil "ResGen"
    2 -Generer l'assemblie à partir du fichier de classe c# 
    3 - instancier l'objet 'ressource' à partir de cette l'assembly pour acceder à ces propirtées qui sont des clé dans le fichers 'resx'.

    au cours de ce traitement je crée un repertoire Temp ou je met les 2 fichiers (.cs et .resource).


    les fichiers "resx" sont mis dans un repertoire a part (ne sont pas embarqués dans l'application) et aprés chaque modification ou ajout , il faudera mettre à jour le fichier de configuration App.config et RELANCER l'APPLICATION

    <configuration>
    <appSettings>
    <add key="ResGenPath" value="C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\ResGen.exe"/> ---> l'outil qui genere la classe à partir du resx
    <add key="ResxDirPath" value="C:\Resx"/> ----> le repertoire ou sont mis les fiches ".resx"
    <add key="Lang" value = "fr,en,es"/>  ----> les langues disponibles
    <add key="fr" value ="Resource_fr"/> ------> le nom du ficher ressource en fr sur la machine
    <add key="en" value ="Resource_en"/>
    <add key="es" value ="Resource_es"/>
    </appSettings>
    </configuration>

     


    voici la classe Ressouce, le code est bien commenté , j'ai testé et ça marche.
    SI VOUS ÊTES SUR VISTA ALORS IL FAUDERA SE CONNECTER EN ADMIN.

     pour activer le compte Admin sur Vista:

    lancer "Cmd"  par click droi de la sourie "Executeur en tant qu'administrateur"
    puis tapez:

    net use Administrateur votremotdepasse
    net use Administrateur /active:yes

    puis connecter vous avec ce compte.


    Classe "Ressource.cs" le code est bien  expliqué :

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading;
    using System.Diagnostics;
    using System.Security;
    using Microsoft.CSharp;
    using System.CodeDom.Compiler;
    using System.Configuration;
    using System.IO;
    using Microsoft.CSharp; 
    using System.CodeDom.Compiler;
    using System.Reflection;
    
    
    namespace AppendResx
    {
         
        public static  class Ressource
        {
    
            static string TempDir = Environment.CurrentDirectory + "/Temp";
            static string Lang = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
            static string ResxFileName=null;
            static string ResxFileNameWithExtention = null;
            static string ResxFileNameRessourceExtention = null;
            static string ResxClassFileName = null;
            static string ResxClassNameSpace = null;
            static string ResxAssemblyName = null;
    
    
    
            /// <summary>
            /// Init
            /// </summary>
            /// <param name="lang"></param>
            private static void Init(string lang)
            {
                ResxFileName = ConfigurationSettings.AppSettings[lang.ToLower()];
                ResxAssemblyName = ResxFileName + ".dll";
                ResxClassNameSpace = "AppendResx." + ResxFileName;
                ResxFileNameWithExtention = ResxFileName + ".resx";
                ResxFileNameRessourceExtention = string.Format("AppendResx.{0}.resources", ResxFileName);
                ResxClassFileName = ResxFileName + ".cs";
    
            }
    
            /// <summary>
            /// Executer cette methode au lancement de l'application
            /// </summary>
            public static void Run()
            {
                // pour toutes les langueq definies dans le App.Config
                foreach (string lang in ConfigurationSettings.AppSettings["Lang"].Split(','))
                {
                   
                    Init(lang);
    
                    if (File.Exists(Path.Combine(ConfigurationSettings.AppSettings["ResxDirPath"], ResxFileNameWithExtention)))
                    {
                        // 1.Generer la fichier ".Cs" ainsi que le fichier binaire ".ressources"
                        GenerateClassFromResxFile();
    
                        //2.Compiler la classe nouvelle créer + le fichier (.ressource)
                        CompliResxClass();
                    }
                }
            }
    
          
            /// <summary>
            /// Recuperer la valeur de l'item en specifiant sa clé
            /// </summary>
            /// <param name="key"></param>
            public static string GetValueByLang(string key)
            {
    
                //3. recuperer la valuer de l'item souhaité.
    
                Init(Lang);
    
                if (File.Exists(Path.Combine(ConfigurationSettings.AppSettings["ResxDirPath"], ResxFileNameWithExtention)))
                {
    
                    // Chargement de l'assembly nouvellement créée
                    Assembly asm = Assembly.LoadFrom(Path.Combine(Environment.CurrentDirectory, ResxAssemblyName));
                    Type t = asm.GetType(ResxClassNameSpace);
    
    
    
                    BindingFlags bflags = BindingFlags.DeclaredOnly | BindingFlags.Public
                                | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
    
                    // Instantiation de l'objet en Appellant son constructeur (Resource_en)
                    Object obj = t.InvokeMember(ResxFileName, bflags |
                        BindingFlags.CreateInstance, null, null, null);
    
                    // Invoquer l'item souhaité 
                    object x = t.InvokeMember(key, bflags | BindingFlags.GetProperty,
                            null, obj, null);
    
                    if (x != null)
                        return x.ToString();
    
    
                }
    
                return " Element Not FOund" ;
    
            }
    
           /// <summary>
           /// Generer la classe Csharp à partir du ficher Resx en utilisant l'outil "ResGen.exe"
           /// </summary>
           /// <param name="resxPath"></param>
            private static  void GenerateClassFromResxFile()
            {
                System.Diagnostics.Process process = null;
                System.Diagnostics.ProcessStartInfo processStartInfo;
    
                processStartInfo = new System.Diagnostics.ProcessStartInfo();
    
    
    
                processStartInfo.FileName = ConfigurationSettings.AppSettings["ResGenPath"];
                processStartInfo.UseShellExecute = true;
                processStartInfo.Arguments = Path.Combine(ConfigurationSettings.AppSettings["ResxDirPath"], ResxFileNameWithExtention) + " " + ResxFileNameRessourceExtention  + " /str:c#,AppendResx" +
                                                             "," + ResxFileName + "," + ResxClassFileName;
    
                //processStartInfo.Arguments = string.Format(@"{O} {1} {4},AppendResx,{2},{3}", Path.Combine(ConfigurationSettings.AppSettings["ResxDirPath"],ResxFileNameWithExtention),
                //                                            ResxFileNameRessourceExtention,ResxFileName,ResxClassFileName,"/str:c#") ;
                processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                processStartInfo.CreateNoWindow = true;
                processStartInfo.Verb = "runas";
    
                try
                {
                    process = System.Diagnostics.Process.Start(processStartInfo);
                    
                    //Clean Up Temp DIR
                    if (Directory.Exists(TempDir))
                        Directory.Delete(TempDir);
    
    
                    Directory.CreateDirectory(TempDir);
    
                    File.Move(ResxFileNameRessourceExtention, Path.Combine(TempDir, ResxFileNameRessourceExtention));
                    File.Delete(ResxFileNameRessourceExtention);
    
                    File.Move(ResxClassFileName, Path.Combine(TempDir, ResxClassFileName));
                    File.Delete(ResxClassFileName);
    
                }
                catch (Exception ex)
                {
                    return;
                }
                finally
                {
                    if (process != null)
                    {
                        process.Dispose();
                    }
                }
    
    
            }
    
            /// <summary>
            /// Compiler le fichier Cs nouvellement généré 
            /// </summary>
            /// <param name="classPath"></param>
            private static void CompliResxClass()
            {
    
           
                // Creer le Compilateur C# 
                CSharpCodeProvider csCompiler = new CSharpCodeProvider();
                ICodeCompiler iCodeCompiler = csCompiler.CreateCompiler();
    
                // renseigner les parametres
                CompilerParameters compilerParams = new CompilerParameters();
                compilerParams.OutputAssembly = ResxAssemblyName;
                compilerParams.ReferencedAssemblies.Add("system.dll");
    
                // Embarqué la ressource dans la DLL
                compilerParams.EmbeddedResources.Add(Path.Combine(TempDir, ResxFileNameRessourceExtention));
    
                // pour generer la DLL
                compilerParams.GenerateExecutable = false;
    
                // Creer l'assembly dans le repertoire de l'application
                iCodeCompiler.CompileAssemblyFromFile(compilerParams, Path.Combine(TempDir, ResxClassFileName));
    
    
                //Clean Up Temp DIR
                if (Directory.Exists(TempDir))
                    Directory.Delete(TempDir,true );
    
            }
        }
    
    }
    



    j'espere que ça vous aidera dans votre projet.



    Mehdi.

    mercredi 13 janvier 2010 11:23
  • Merci medhius. Nous allons regarder cela avec attention. Nous avions écarté cette solution de compilation de part justement le problème de droit potentiel et de la présence de l'exe ResGen sur une install standard du framework.
    Dans le cadre d'une application web le compte windows exécutant le pool d'application n'aura probablement pas les droits suffisants.

    Ci dessous voici le premier jet de notre solution.
    Nous utilisons la classe ResXResourceReader pour lire les fichiers resx sans avoir besoin de les compiler.
    Lors de la première lecture les couples clé valeur sont mis en cache dans un dictionnaire.

    Pour lire une valeur, il faut donc faire : ResXStringHelper.GetLocalizedString("MyChaine", "MyResource")
    ( et il faudrait donc modifier les accesseurs des classes qui avaient été générés mais c'est assez rapide )

    using System;
    using System.Collections.Generic;
    using System.Resources;
    using System.Collections;
    using System.IO;
    
    namespace Ressources
    {
      public class ResXStringHelper
      {
        #region Var
    
        // cache les valeurs par nom de resx ( et donc par culture )
        private static Dictionary<string, Dictionary<string, string>> DicoCache = new Dictionary<string, Dictionary<string, string>>();
    
        #endregion
    
        #region Méthodes privées
    
        /// <summary>
        /// Obtient le dico si il existe sinon le crée
        /// </summary>
        /// <param name="ressourceFile"></param>
        /// <param name="cultureInfo"></param>
        /// <returns></returns>
        private static Dictionary<string, string> GetDico( string ressourceFile, string cultureInfo )
        {
          string resxName = GetResxName(ressourceFile,cultureInfo);
    
          // dico déjà crée pour cette culture
          if (!DicoCache.ContainsKey(resxName))
          {
            // on le crée et l'alimente
            StoreValuesFromResx(ressourceFile, cultureInfo);
          }
    
          return DicoCache[resxName];
        }
    
        /// <summary>
        /// Formate le nom du resx
        /// </summary>
        /// <param name="ressourceFile"></param>
        /// <param name="cultureInfo"></param>
        /// <returns></returns>
        private static string GetResxName(string ressourceFile, string cultureInfo)
        {
          string resxName;
    
          // construit nom du resx en focntion de la culture
          if (String.IsNullOrEmpty(cultureInfo))
            resxName = String.Format("{0}.resx", ressourceFile);
          else
            resxName = String.Format("{0}.{1}.resx", ressourceFile, cultureInfo);
    
          return resxName;
        }
    
        /// <summary>
        /// Place les clés/valeurs en cache
        /// </summary>
        /// <param name="ressourceFile"></param>
        /// <param name="cultureInfo"></param>
        private static void StoreValuesFromResx(string ressourceFile, string cultureInfo )
        {
          // nom resx
          string resxName = GetResxName(ressourceFile, cultureInfo);
    
          // path du Resx
          Uri uri = new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
          string pathResx = Path.Combine(Path.GetDirectoryName(uri.AbsolutePath), resxName);
    
          // gestion dictionnaire
          DicoCache.Add(resxName, new Dictionary<string, string>());
    
          // lecture resx
          if (File.Exists(pathResx))
          {
            ResXResourceReader resxReader = new ResXResourceReader(pathResx);
    
            // clés/valeurs dans dico de cache
            foreach (DictionaryEntry d in resxReader)
            {
              DicoCache[resxName].Add(d.Key.ToString(), d.Value.ToString());
            }
          }
        }
    
        #endregion
    
        #region Méthodes publiques
    
        /// <summary>
        /// Obtient une ressource localisée en fonction de la culture
        /// </summary>
        /// <param name="key"></param>
        /// <param name="ressourceFile"></param>
        /// <returns></returns>
        public static string GetLocalizedString(string key, string ressourceFile)
        {
          return GetLocalizedString(key, ressourceFile, System.Threading.Thread.CurrentThread.CurrentUICulture.Name);
        }
    
        /// <summary>
        /// Obtient une ressource localisée en précisant le nom de la culture
        /// Si on ne trouve pas la culture exacte ( pas de test sur la région uniquement )
        /// on travaille sur la culture neutre
        /// Par défaut retourne une chaine vide
        /// </summary>
        /// <param name="key"></param>
        /// <param name="ressourceFile"></param>
        /// <param name="cultureInfo"></param>
        /// <returns></returns>
        public static string GetLocalizedString(string key, string ressourceFile, string cultureInfo)
        {
          string searchValue = null;
      
          // pour cette culture ou culture neutre
          if (GetDico(ressourceFile, cultureInfo).ContainsKey(key))
            searchValue = GetDico(ressourceFile, cultureInfo)[key];
          else if (GetDico(ressourceFile, null).ContainsKey(key))
            searchValue = GetDico(ressourceFile, null)[key];
          else
            searchValue = string.Empty;
    
          return searchValue;
        }
    
        #endregion
      }
    }
    
    mercredi 13 janvier 2010 11:51
    Modérateur

Toutes les réponses

  • Si j'ai bien compris, j'ai une application  "app1" qui tourne avec 2  langues chacune dans ressource (resx). cependant je veux rajouter une nouvelle langue;
    donc je dois trouver le moyen d'utiliser cette 3 ieme ressource (resx) SANS TOUCHER au code source de "app1" c'est bien ça?
    mardi 12 janvier 2010 18:24
  • Bonjour,

    Oui je pense que tu as compris.
    Des fichiers resx sous forme XML sont utilisés par l'application pour gérer les langues, et l'utilisateur doit pouvoir ajouter des langues, et modifier les fichiers de ressource pour chacune des langues.
    Nikho, si personne n'a encore posté quelque chose qui marche d'ici à ce que Mathieu passe, il devrait te proposer quelque chose ;)

    Cordialement,

    Thomas

    PS : Haha, Mat', comment je te mets la pression :p
    PS2 : Et me fais pas mentir ;)
    Thomas Aimonetti - C# - Sharplog Engineering - http://www.sharplog.fr
    mardi 12 janvier 2010 18:28
  • VOici ma proposition:

    - Mettre toutes les langues dans un seul fichier XML de la façon suivante:

    <Root>
    <Controles>
    <Controle Id="Ecran1.Label1">
    <lang  fr="Lavel1_fr En="Label1_en" ..>
    </Controle>
    ....
    </Root>

    chaque controle dans l'application doit avoir une entré dans le fichier XML. l'exemple ci-dessus est pour le controle label "label1" qui se trouve dans le formulaire "Ecran1"

    - L'affectation du libellé du control "Label1":

      Lable1.Text= Ressouce.GetLangValueById(Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName, string.Format("{0}.{1}", label1.Parent.Name, label1.Name));



    - Creer une classe Static "Ressource" qui contient la Methode "GetLangValueById" qui parse le fichier XML pour recuperer la valeur via 2 clés:
        le Controle Id et la langue.

    mardi 12 janvier 2010 19:11
  • Bonsoir,

    oui c'est bien cela. Une application WinForm ou une application Web référence un projet de type bibliothèque de classe.

    Cette bibliothèque Ressources contient par exemple :

    - un fichier MyResource.resx qui génére dans visual studio la classe Ressources.MyResource ( via l'outil PublicResXFileCodeGenerator )
    - un fichier MyResource.fr-FR.resx
    - un fichier MyResource.en-US.resx
    .....

    Aujourd'hui cette bibliothèque permet d'accéder facilement aux ressources localisées via la classe générée. Par exemple Ressources.MyResource.MyString retourne la valeur de la clé MyString en fonction de la culture définie dans Thread.CurrentThread.CurrentUICulture.
    Tout ceci ne fonctionne que si MyResource.resx, MyResource.fr-FR.resx ... sont définies comme ressource incorporée.

    En fait le problème et de pouvoir faire la même chose sans que ces ressources soient incorporées pour laisser possible la modification voir l'ajout de resx...

    Merci de votre aide, car pour le moment nous n'avons pas de solution très élégante.

    PS: Mathieu on compte sur toi ;)

    mardi 12 janvier 2010 19:18
    Modérateur
  • AH oki, donc l'utilisation des fichiers (.resx) est imperative.

    Je m'investi, j'aime bien ce challenge ^^
    mardi 12 janvier 2010 19:24
  • Bonsoir,

    Merci Thomas, cela me met un peu la pression :-) 

    Je partage l'avis de Mehdius, le projet est intéressant !

    Je regarde aussi de  mon côté comment faire cela proprement (cela pourra sûrement nous servir aussi). 

    Amicalement, 

    Mathieu 
    Mathieu Francesch Sharplog Engineering
    mardi 12 janvier 2010 19:54
  • Bonjour,
    Voici la solution que je propose:

    1- Generer une un fichier ".cs" et un fichier binaire ".resource" à partir du ficher ".resx" avec l'outil "ResGen"
    2 -Generer l'assemblie à partir du fichier de classe c# 
    3 - instancier l'objet 'ressource' à partir de cette l'assembly pour acceder à ces propirtées qui sont des clé dans le fichers 'resx'.

    au cours de ce traitement je crée un repertoire Temp ou je met les 2 fichiers (.cs et .resource).


    les fichiers "resx" sont mis dans un repertoire a part (ne sont pas embarqués dans l'application) et aprés chaque modification ou ajout , il faudera mettre à jour le fichier de configuration App.config et RELANCER l'APPLICATION

    <configuration>
    <appSettings>
    <add key="ResGenPath" value="C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\ResGen.exe"/> ---> l'outil qui genere la classe à partir du resx
    <add key="ResxDirPath" value="C:\Resx"/> ----> le repertoire ou sont mis les fiches ".resx"
    <add key="Lang" value = "fr,en,es"/>  ----> les langues disponibles
    <add key="fr" value ="Resource_fr"/> ------> le nom du ficher ressource en fr sur la machine
    <add key="en" value ="Resource_en"/>
    <add key="es" value ="Resource_es"/>
    </appSettings>
    </configuration>

     


    voici la classe Ressouce, le code est bien commenté , j'ai testé et ça marche.
    SI VOUS ÊTES SUR VISTA ALORS IL FAUDERA SE CONNECTER EN ADMIN.

     pour activer le compte Admin sur Vista:

    lancer "Cmd"  par click droi de la sourie "Executeur en tant qu'administrateur"
    puis tapez:

    net use Administrateur votremotdepasse
    net use Administrateur /active:yes

    puis connecter vous avec ce compte.


    Classe "Ressource.cs" le code est bien  expliqué :

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading;
    using System.Diagnostics;
    using System.Security;
    using Microsoft.CSharp;
    using System.CodeDom.Compiler;
    using System.Configuration;
    using System.IO;
    using Microsoft.CSharp; 
    using System.CodeDom.Compiler;
    using System.Reflection;
    
    
    namespace AppendResx
    {
         
        public static  class Ressource
        {
    
            static string TempDir = Environment.CurrentDirectory + "/Temp";
            static string Lang = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
            static string ResxFileName=null;
            static string ResxFileNameWithExtention = null;
            static string ResxFileNameRessourceExtention = null;
            static string ResxClassFileName = null;
            static string ResxClassNameSpace = null;
            static string ResxAssemblyName = null;
    
    
    
            /// <summary>
            /// Init
            /// </summary>
            /// <param name="lang"></param>
            private static void Init(string lang)
            {
                ResxFileName = ConfigurationSettings.AppSettings[lang.ToLower()];
                ResxAssemblyName = ResxFileName + ".dll";
                ResxClassNameSpace = "AppendResx." + ResxFileName;
                ResxFileNameWithExtention = ResxFileName + ".resx";
                ResxFileNameRessourceExtention = string.Format("AppendResx.{0}.resources", ResxFileName);
                ResxClassFileName = ResxFileName + ".cs";
    
            }
    
            /// <summary>
            /// Executer cette methode au lancement de l'application
            /// </summary>
            public static void Run()
            {
                // pour toutes les langueq definies dans le App.Config
                foreach (string lang in ConfigurationSettings.AppSettings["Lang"].Split(','))
                {
                   
                    Init(lang);
    
                    if (File.Exists(Path.Combine(ConfigurationSettings.AppSettings["ResxDirPath"], ResxFileNameWithExtention)))
                    {
                        // 1.Generer la fichier ".Cs" ainsi que le fichier binaire ".ressources"
                        GenerateClassFromResxFile();
    
                        //2.Compiler la classe nouvelle créer + le fichier (.ressource)
                        CompliResxClass();
                    }
                }
            }
    
          
            /// <summary>
            /// Recuperer la valeur de l'item en specifiant sa clé
            /// </summary>
            /// <param name="key"></param>
            public static string GetValueByLang(string key)
            {
    
                //3. recuperer la valuer de l'item souhaité.
    
                Init(Lang);
    
                if (File.Exists(Path.Combine(ConfigurationSettings.AppSettings["ResxDirPath"], ResxFileNameWithExtention)))
                {
    
                    // Chargement de l'assembly nouvellement créée
                    Assembly asm = Assembly.LoadFrom(Path.Combine(Environment.CurrentDirectory, ResxAssemblyName));
                    Type t = asm.GetType(ResxClassNameSpace);
    
    
    
                    BindingFlags bflags = BindingFlags.DeclaredOnly | BindingFlags.Public
                                | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
    
                    // Instantiation de l'objet en Appellant son constructeur (Resource_en)
                    Object obj = t.InvokeMember(ResxFileName, bflags |
                        BindingFlags.CreateInstance, null, null, null);
    
                    // Invoquer l'item souhaité 
                    object x = t.InvokeMember(key, bflags | BindingFlags.GetProperty,
                            null, obj, null);
    
                    if (x != null)
                        return x.ToString();
    
    
                }
    
                return " Element Not FOund" ;
    
            }
    
           /// <summary>
           /// Generer la classe Csharp à partir du ficher Resx en utilisant l'outil "ResGen.exe"
           /// </summary>
           /// <param name="resxPath"></param>
            private static  void GenerateClassFromResxFile()
            {
                System.Diagnostics.Process process = null;
                System.Diagnostics.ProcessStartInfo processStartInfo;
    
                processStartInfo = new System.Diagnostics.ProcessStartInfo();
    
    
    
                processStartInfo.FileName = ConfigurationSettings.AppSettings["ResGenPath"];
                processStartInfo.UseShellExecute = true;
                processStartInfo.Arguments = Path.Combine(ConfigurationSettings.AppSettings["ResxDirPath"], ResxFileNameWithExtention) + " " + ResxFileNameRessourceExtention  + " /str:c#,AppendResx" +
                                                             "," + ResxFileName + "," + ResxClassFileName;
    
                //processStartInfo.Arguments = string.Format(@"{O} {1} {4},AppendResx,{2},{3}", Path.Combine(ConfigurationSettings.AppSettings["ResxDirPath"],ResxFileNameWithExtention),
                //                                            ResxFileNameRessourceExtention,ResxFileName,ResxClassFileName,"/str:c#") ;
                processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                processStartInfo.CreateNoWindow = true;
                processStartInfo.Verb = "runas";
    
                try
                {
                    process = System.Diagnostics.Process.Start(processStartInfo);
                    
                    //Clean Up Temp DIR
                    if (Directory.Exists(TempDir))
                        Directory.Delete(TempDir);
    
    
                    Directory.CreateDirectory(TempDir);
    
                    File.Move(ResxFileNameRessourceExtention, Path.Combine(TempDir, ResxFileNameRessourceExtention));
                    File.Delete(ResxFileNameRessourceExtention);
    
                    File.Move(ResxClassFileName, Path.Combine(TempDir, ResxClassFileName));
                    File.Delete(ResxClassFileName);
    
                }
                catch (Exception ex)
                {
                    return;
                }
                finally
                {
                    if (process != null)
                    {
                        process.Dispose();
                    }
                }
    
    
            }
    
            /// <summary>
            /// Compiler le fichier Cs nouvellement généré 
            /// </summary>
            /// <param name="classPath"></param>
            private static void CompliResxClass()
            {
    
           
                // Creer le Compilateur C# 
                CSharpCodeProvider csCompiler = new CSharpCodeProvider();
                ICodeCompiler iCodeCompiler = csCompiler.CreateCompiler();
    
                // renseigner les parametres
                CompilerParameters compilerParams = new CompilerParameters();
                compilerParams.OutputAssembly = ResxAssemblyName;
                compilerParams.ReferencedAssemblies.Add("system.dll");
    
                // Embarqué la ressource dans la DLL
                compilerParams.EmbeddedResources.Add(Path.Combine(TempDir, ResxFileNameRessourceExtention));
    
                // pour generer la DLL
                compilerParams.GenerateExecutable = false;
    
                // Creer l'assembly dans le repertoire de l'application
                iCodeCompiler.CompileAssemblyFromFile(compilerParams, Path.Combine(TempDir, ResxClassFileName));
    
    
                //Clean Up Temp DIR
                if (Directory.Exists(TempDir))
                    Directory.Delete(TempDir,true );
    
            }
        }
    
    }
    



    j'espere que ça vous aidera dans votre projet.



    Mehdi.

    mercredi 13 janvier 2010 11:23
  • Merci medhius. Nous allons regarder cela avec attention. Nous avions écarté cette solution de compilation de part justement le problème de droit potentiel et de la présence de l'exe ResGen sur une install standard du framework.
    Dans le cadre d'une application web le compte windows exécutant le pool d'application n'aura probablement pas les droits suffisants.

    Ci dessous voici le premier jet de notre solution.
    Nous utilisons la classe ResXResourceReader pour lire les fichiers resx sans avoir besoin de les compiler.
    Lors de la première lecture les couples clé valeur sont mis en cache dans un dictionnaire.

    Pour lire une valeur, il faut donc faire : ResXStringHelper.GetLocalizedString("MyChaine", "MyResource")
    ( et il faudrait donc modifier les accesseurs des classes qui avaient été générés mais c'est assez rapide )

    using System;
    using System.Collections.Generic;
    using System.Resources;
    using System.Collections;
    using System.IO;
    
    namespace Ressources
    {
      public class ResXStringHelper
      {
        #region Var
    
        // cache les valeurs par nom de resx ( et donc par culture )
        private static Dictionary<string, Dictionary<string, string>> DicoCache = new Dictionary<string, Dictionary<string, string>>();
    
        #endregion
    
        #region Méthodes privées
    
        /// <summary>
        /// Obtient le dico si il existe sinon le crée
        /// </summary>
        /// <param name="ressourceFile"></param>
        /// <param name="cultureInfo"></param>
        /// <returns></returns>
        private static Dictionary<string, string> GetDico( string ressourceFile, string cultureInfo )
        {
          string resxName = GetResxName(ressourceFile,cultureInfo);
    
          // dico déjà crée pour cette culture
          if (!DicoCache.ContainsKey(resxName))
          {
            // on le crée et l'alimente
            StoreValuesFromResx(ressourceFile, cultureInfo);
          }
    
          return DicoCache[resxName];
        }
    
        /// <summary>
        /// Formate le nom du resx
        /// </summary>
        /// <param name="ressourceFile"></param>
        /// <param name="cultureInfo"></param>
        /// <returns></returns>
        private static string GetResxName(string ressourceFile, string cultureInfo)
        {
          string resxName;
    
          // construit nom du resx en focntion de la culture
          if (String.IsNullOrEmpty(cultureInfo))
            resxName = String.Format("{0}.resx", ressourceFile);
          else
            resxName = String.Format("{0}.{1}.resx", ressourceFile, cultureInfo);
    
          return resxName;
        }
    
        /// <summary>
        /// Place les clés/valeurs en cache
        /// </summary>
        /// <param name="ressourceFile"></param>
        /// <param name="cultureInfo"></param>
        private static void StoreValuesFromResx(string ressourceFile, string cultureInfo )
        {
          // nom resx
          string resxName = GetResxName(ressourceFile, cultureInfo);
    
          // path du Resx
          Uri uri = new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
          string pathResx = Path.Combine(Path.GetDirectoryName(uri.AbsolutePath), resxName);
    
          // gestion dictionnaire
          DicoCache.Add(resxName, new Dictionary<string, string>());
    
          // lecture resx
          if (File.Exists(pathResx))
          {
            ResXResourceReader resxReader = new ResXResourceReader(pathResx);
    
            // clés/valeurs dans dico de cache
            foreach (DictionaryEntry d in resxReader)
            {
              DicoCache[resxName].Add(d.Key.ToString(), d.Value.ToString());
            }
          }
        }
    
        #endregion
    
        #region Méthodes publiques
    
        /// <summary>
        /// Obtient une ressource localisée en fonction de la culture
        /// </summary>
        /// <param name="key"></param>
        /// <param name="ressourceFile"></param>
        /// <returns></returns>
        public static string GetLocalizedString(string key, string ressourceFile)
        {
          return GetLocalizedString(key, ressourceFile, System.Threading.Thread.CurrentThread.CurrentUICulture.Name);
        }
    
        /// <summary>
        /// Obtient une ressource localisée en précisant le nom de la culture
        /// Si on ne trouve pas la culture exacte ( pas de test sur la région uniquement )
        /// on travaille sur la culture neutre
        /// Par défaut retourne une chaine vide
        /// </summary>
        /// <param name="key"></param>
        /// <param name="ressourceFile"></param>
        /// <param name="cultureInfo"></param>
        /// <returns></returns>
        public static string GetLocalizedString(string key, string ressourceFile, string cultureInfo)
        {
          string searchValue = null;
      
          // pour cette culture ou culture neutre
          if (GetDico(ressourceFile, cultureInfo).ContainsKey(key))
            searchValue = GetDico(ressourceFile, cultureInfo)[key];
          else if (GetDico(ressourceFile, null).ContainsKey(key))
            searchValue = GetDico(ressourceFile, null)[key];
          else
            searchValue = string.Empty;
    
          return searchValue;
        }
    
        #endregion
      }
    }
    
    mercredi 13 janvier 2010 11:51
    Modérateur
  • J'ai cru comprendre que votre but est d'utiliser les classes generés à partir de Resx (donc compilation pour chaque modofication).


    C'est quoi l'inonveniant avec la solution que vous utilisez actuellement ? est-ce que ça marche ?


    mercredi 13 janvier 2010 12:16
  • Notre but était déjà de vérifier que nous ne sommes pas passé à côté d'une solution plus directe fournit par le framework et de ne pas faire de modification en dehors de la bibliothèque de classe dédiée à la gestion des ressources.

    Les classes générées par visual studio sont assez simples pour pouvoir se passer du générateur, il faudra juste coder les get à la main, ou utiliser une expression régulière pour faire des modifs sur les classes générées....à voir.

    La classe ci dessus vient d'être écrite, d'après les premiers tests elle fonctionne bien pour le moment mais il n'y a que des tests plus poussés qui permettront de la valider. Personnellement je n'y vois pas d'inconvénient majeur hormis que cela semble un peu hors "standard".

    Nous sommes preneurs de toutes remarques, commentaires.

    Encore une fois merci de l'intérêt porté à cette question.
    mercredi 13 janvier 2010 12:35
    Modérateur
  • tu peux toujours regarder le code Source de tool "ResXFilePublicCodeGenerator", donc vous "generer la classe CS" sans le ResGen! puis vous compiler

    http://altinoren.com/PermaLink,guid,5b69e6d6-86b0-4717-889a-94db78ff04b2.aspx


    bonne chance.
    Mehdi
    mercredi 13 janvier 2010 20:23
  • Merci pour ce lien et pour ton aide sur ce sujet.
    Nous avons pour le moment mis en place la solution à base de dictionnaire.
    Je marque néanmoins ta proposition également comme réponse cela peut servir à d'autres.

    Encore merci.
    jeudi 14 janvier 2010 10:59
    Modérateur