Как локализовать поиск строковых ресурсов из всех потоков в приложении? - PullRequest
2 голосов
/ 07 октября 2009

Рекомендуется установить текущую культуру потока приложения, чтобы при поиске ресурсов использовался правильный язык.

К сожалению, это не устанавливает культуру для других потоков. Это особенно проблема для потоков пула потоков.

Вопрос заключается в следующем: как можно правильно разрешить поиск строковых ресурсов из потоков пула потоков с наименьшим количеством дополнительного кода?


Edit:

Проблема в том, что этот код генерируется из таблицы строк.

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

В этом случае 'resourceCulture' неправильно настроен для потока пула потоков. Я мог бы просто вызвать 'ResourceManager.GetString ("IDS_MYSTRING", correctCulture); " но это означало бы потерю преимуществ проверки времени компиляции на наличие строки.

Теперь мне интересно, исправить ли это, чтобы изменить видимость таблицы строк на public и установить свойство Culture всех сборок, перечисленных с использованием отражения.

Ответы [ 4 ]

2 голосов
/ 19 октября 2009

Для тех, кто пытается сделать это в будущем, я набрал следующий код:

/// <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;
            }
        }
    }
}
1 голос
/ 07 октября 2009

Я использую строковые ресурсы из файла insert ... 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 и запустите отладчик.

Ура, Florian

0 голосов
/ 07 октября 2009

Тогда, если это обработчик сокета, просто переопределите тип обратного вызова и зарегистрируйте свой асинхронный обратный вызов с фабрикой локализованного обработчика, например так:

    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);
0 голосов
/ 07 октября 2009

Вы пытались получить доступ к Application.CurrentCulture вместо Thread.CurrentThread.CurrentCulture?

...