【问题标题】:How to localize string resource lookups from all threads in an application?如何本地化来自应用程序中所有线程的字符串资源查找?
【发布时间】:2009-10-07 09:25:23
【问题描述】:

推荐的最佳做法是设置应用程序线程的当前文化,以使资源查找能够使用正确的语言。

不幸的是,这不会为任何其他线程设置文化。这对于线程池线程来说尤其是一个问题。

问题是:如何设置启用字符串资源查找以从具有最少额外管道代码的线程池线程正确本地化?


编辑:

问题是这段代码是从字符串表生成的。

internal static string IDS_MYSTRING {
    get {
        return ResourceManager.GetString("IDS_MYSTRING", resourceCulture);
    }
}

在这种情况下,'resourceCulture' 没有为线程池线程正确设置。我可以调用'ResourceManager.GetString("IDS_MYSTRING", correctCulture);'但这意味着失去编译时检查字符串是否存在的好处。

我现在想知道解决方法是否是将字符串表的可见性更改为公共并设置使用反射枚举的所有程序集的 Culture 属性。

【问题讨论】:

    标签: .net localization multithreading threadpool


    【解决方案1】:

    对于将来尝试此操作的任何人,我最终得到了以下代码:

    /// <summary>
    /// Encapsulates the culture to use for localisation.
    /// This class exists so that the culture to use for
    /// localisation is defined in one place.
    /// Setting the Culture property will change the culture and language
    /// used by all assemblies, whether they are loaded before or after
    /// the property is changed.
    /// </summary>
    public class LocalisationCulture
    {
        private CultureInfo                 cultureInfo         = Thread.CurrentThread.CurrentUICulture;
        private static LocalisationCulture  instance            = new LocalisationCulture();
        private List<Assembly>              loadedAssemblies    = new List<Assembly>();
        private static ILog                 logger              = LogManager.GetLogger(typeof(LocalisationCulture));
        private object                      syncRoot            = new object();
    
        private LocalisationCulture()
        {
            AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(this.OnAssemblyLoadEvent);
    
            lock(this.syncRoot)
            {
                foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    if(LocalisationCulture.IsAssemblyResourceContaining(assembly))
                    {
                        this.loadedAssemblies.Add(assembly);
                    }
                }
            }
        }
    
        /// <summary>
        /// The singleton instance of the LocalisationCulture class.
        /// </summary>
        public static LocalisationCulture Instance
        {
            get
            {
                return LocalisationCulture.instance;
            }
        }
    
        /// <summary>
        /// The culture that all loaded assemblies will use for localisation.
        /// Setting the Culture property will change the culture and language
        /// used by all assemblies, whether they are loaded before or after
        /// the property is changed.
        /// </summary>
        public CultureInfo Culture
        {
            get
            {
                return this.cultureInfo;
            }
    
            set
            {
                // Set the current culture to enable resource look ups to
                // use the correct language.
    
                Thread.CurrentThread.CurrentUICulture = value;
    
                // Store the culture info so that it can be retrieved
                // elsewhere throughout the applications.
    
                this.cultureInfo = value;
    
                // Set the culture to use for string look ups for all loaded assemblies.
    
                this.SetResourceCultureForAllLoadedAssemblies();
            }
        }
    
        private static bool IsAssemblyResourceContaining(Assembly assembly)
        {
            Type[] types = assembly.GetTypes();
    
            foreach(Type t in types)
            {
                if(     t.IsClass
                    &&  t.Name == "Resources")
                {
                    return true;
                }
            }
    
            return false;
        }
    
        private void OnAssemblyLoadEvent(object sender, AssemblyLoadEventArgs args)
        {
            if(!LocalisationCulture.IsAssemblyResourceContaining(args.LoadedAssembly))
            {
                return;
            }
    
            lock(this.syncRoot)
            {
                this.loadedAssemblies.Add(args.LoadedAssembly);
    
                this.SetResourceCultureForAssembly(args.LoadedAssembly);
            }
        }
    
        private void SetResourceCultureForAllLoadedAssemblies()
        {
            lock(this.syncRoot)
            {
                foreach(Assembly assembly in this.loadedAssemblies)
                {
                    this.SetResourceCultureForAssembly(assembly);
                }
            }
        }
    
        private void SetResourceCultureForAssembly(Assembly assembly)
        {
            Type[] types = assembly.GetTypes();
    
            foreach(Type t in types)
            {
                if(     t.IsClass
                    &&  t.Name == "Resources")
                {
                    LocalisationCulture.logger.Debug(String.Format( CultureInfo.InvariantCulture,
                                                                    "Using culture '{0}' for assembly '{1}'",
                                                                    this.cultureInfo.EnglishName,
                                                                    assembly.FullName));
    
                    PropertyInfo propertyInfo = t.GetProperty(  "Culture",
                                                                BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.NonPublic);
    
                    MethodInfo methodInfo = propertyInfo.GetSetMethod(true);
    
                    methodInfo.Invoke(  null,
                                        new object[]{this.cultureInfo} );
    
                    break;
                }
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      我正在使用来自插入... resx 文件和附属程序集的字符串资源。您确定您的文件命名正确吗?

      Resource1.resx:

      <!-- snip-->
      <data name="foo" xml:space="preserve">
          <value>bar</value>
        </data>
      

      Resource1.FR-fr.resx

      <--! le snip -->
        <data name="foo" xml:space="preserve">
          <value>le bar</value>
        </data>
      

      Class1.cs:

      using System;
      using System.Collections.Generic;
      using System.Diagnostics;
      using System.Globalization;
      using System.IO;
      using System.Threading;
      
      namespace Frankenstein
      {
          public class Class1
          {
      
      
      
              struct LocalizedCallback
              {
                  private WaitCallback localized;
      
                  public LocalizedCallback(WaitCallback user)
                  {
                      var uiCult = Thread.CurrentThread.CurrentUICulture;
      
                      // wrap
                      localized = (state) =>
                      {
                          var tp = Thread.CurrentThread;
                          var oldUICult = tp.CurrentUICulture;
                          try
                          {
                              // set the caller thread's culture for lookup
                              Thread.CurrentThread.CurrentUICulture = uiCult;
      
                              // call the user-supplied callback
                              user(state);
                          }
                          finally
                          {
                              // let's restore the TP thread state
                              tp.CurrentUICulture = oldUICult;
                          }
                      };
      
                  }
      
                  public static implicit operator WaitCallback(LocalizedCallback me)
                  {
                      return me.localized;
                  }
              }
      
              public static void Main(string[] args)
              {
      
                  AutoResetEvent evt = new AutoResetEvent(false);
                  WaitCallback worker = state =>
                  {
                      Console.Out.WriteLine(Resource1.foo);
                      evt.Set();
                  };
      
                  // use default resource
                  Console.Out.WriteLine(">>>>>>>>>>{0}", Thread.CurrentThread.CurrentUICulture);
                  Console.Out.WriteLine("without wrapper");
                  ThreadPool.QueueUserWorkItem(worker);
                  evt.WaitOne();
                  Console.Out.WriteLine("with wrapper");
                  ThreadPool.QueueUserWorkItem(new LocalizedCallback(worker));
                  evt.WaitOne();
      
                  // go froggie
                  Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("FR-fr");
                  Console.Out.WriteLine(">>>>>>>>>>{0}", Thread.CurrentThread.CurrentUICulture);           
                  Console.Out.WriteLine("without wrapper");
                  ThreadPool.QueueUserWorkItem(worker);
                  evt.WaitOne();
                  Console.Out.WriteLine("with wrapper");
                  ThreadPool.QueueUserWorkItem(new LocalizedCallback(worker));
                  evt.WaitOne();
              }
          }
      }
      

      输出:

      >>>>>>>>>>en-US
      without wrapper
      bar
      with wrapper
      bar
      >>>>>>>>>>fr-FR
      without wrapper
      bar
      with wrapper
      le bar
      Press any key to continue . . .
      

      之所以可行,是因为 Resource1.Culture 属性始终设置为 null,因此它回退到默认值 (IE Thread.CurrentThread.UICulture)。

      为了证明这一点,编辑 Resource1.Designer.cs 文件并从类中删除以下属性:

      //[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
      

      然后在 Resource.Culture 属性访问器和 foo 属性访问器中设置断点,并启动调试器。

      干杯, 弗洛里安

      【讨论】:

      • 真正的问题在于字符串表查找。我已经编辑了问题以明确这一点。
      • 我已经修改了示例以使用表查找,并且它正在我的机器上运行(2.0 和 3.5)...
      • 只设置 Resource1.Culture 属性更好吗?
      • 其实不,这正是它在做什么。但是,如果您需要多个线程支持不同的文化,Resource1.Culture 是静态的,因此由所有线程共享。干杯!
      • 谢谢 :) 我误解了这个问题,答案是过度设计的。如果您只需要为所有线程支持 1 个语言环境 -> 设置您的 ResourceManager.Culture 属性,您就可以开始了!干杯!
      【解决方案3】:

      您是否尝试过访问 Application.CurrentCulture 而不是 Thread.CurrentThread.CurrentCulture?

      【讨论】:

      • 真正的问题在于字符串表查找。我已经编辑了问题以明确这一点。
      • Application.CurrentCulture 只是委托给Thread.CurrentThread.CurrentCulture
      【解决方案4】:

      如果它是一个套接字处理程序,只需重新定义回调类型并使用本地化处理程序工厂注册您的异步回调,如下所示:

          struct LocalizedAsyncCallback
          {
              private AsyncCallback localized;
      
              public LocalizedAsyncCallback(AsyncCallback user)
              {
                  var uiCult = Thread.CurrentThread.CurrentUICulture;
      
                  // wrap
                  localized = (state) =>
                  {
                      var tp = Thread.CurrentThread;
                      var oldUICult = tp.CurrentUICulture;
                      try
                      {
                          // set the caller thread's culture for lookup
                          Thread.CurrentThread.CurrentUICulture = uiCult;
      
                          // call the user-supplied callback
                          user(state);
                      }
                      finally
                      {
                          // let's restore the TP thread state
                          tp.CurrentUICulture = oldUICult;
                      }
                  };
      
              }
      
              public static implicit operator AsyncCallback(LocalizedAsyncCallback me)
              {
                  return me.localized;
              }
          }
      

      这是您的异步​​套接字处理程序注册样板:

      Socket sock;
      AsyncCallback socketCallback = result => { };
      sock.BeginReceive(buffer, offset,size, flags, new LocalizedAsyncCallback(socketCallback), state);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-20
        相关资源
        最近更新 更多