How to localize search for strings from all streams in an application?
The recommended recommendation is to set your current application flow culture to enable resource discovery so that you can use the correct language.
Unfortunately, this doesn't set the culture for any other threads. This is especially problematic for streams of threads.
The question arises: how can you configure the correct localization of queries to search for strings from streams of streams of streams with the least amount of additional plumbing code?
Edit:
The problem is that this code is generated from a string table.
internal static string IDS_MYSTRING {
get {
return ResourceManager.GetString("IDS_MYSTRING", resourceCulture);
}
}
The "ResourceCulture" in this scenario is incorrectly configured for the thread's thread. I could just call "ResourceManager.GetString (" IDS_MYSTRING ", correctCulture); but that would lose the benefit of compile-time checking that the string exists.
Now I am wondering if the fix should change the visibility of the string table to the public and set the Culture property for all assemblies listed using reflection.
source to share
For those trying to do this in the future, I got the following code:
/// <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;
}
}
}
}
source to share
I am using string resources from insert ... resx file and satellite assemblies. Are you sure you named your files correctly?
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 culture for lookup
Thread.CurrentThread.CurrentUICulture = uiCult;
// call the user-supplied callback
user(state);
}
finally
{
// let 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();
}
}
}
Output:
>>>>>>>>>>en-US
without wrapper
bar
with wrapper
bar
>>>>>>>>>>fr-FR
without wrapper
bar
with wrapper
le bar
Press any key to continue . . .
This is because the Resource1.Culture property is always null, so it falls back to the default (IE Thread.CurrentThread.UICulture).
To prove this, edit the Resource1.Designer.cs file and remove the following attribute from the class:
//[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
Then set a breakpoint on the Resource.Culture accessor and foo property accessors and start the debugger.
Cheers, Florian
source to share
Then, if it's a socket handler, just override the callback type and register an async callback with a localized factory handler like this:
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 culture for lookup
Thread.CurrentThread.CurrentUICulture = uiCult;
// call the user-supplied callback
user(state);
}
finally
{
// let restore the TP thread state
tp.CurrentUICulture = oldUICult;
}
};
}
public static implicit operator AsyncCallback(LocalizedAsyncCallback me)
{
return me.localized;
}
}
And here is your async socket handler registration template:
Socket sock;
AsyncCallback socketCallback = result => { };
sock.BeginReceive(buffer, offset,size, flags, new LocalizedAsyncCallback(socketCallback), state);
source to share