3

Problem:

I have a C# .NET 2.0 application developed on Windows 7 that has translated resources for multiple languages (ex. zh-CHS for Chinese, es for Spanish, etc.).

I have a customer who wants to run their Windows 7 OS in English, but run my .NET application in Spanish (es).

My application is multi-threaded, so just changing the culture of the main GUI thread is not sufficient for my needs (trust me, I tried). This is because other strings displayed to the user through the GUI are generated on other threads. In order to get 100% complete coverage, I would need to set the culture of each individual thread manually to ensure all text from resource files is in the correct language. Because my product is basically a framework for other plugins that other development groups write, I don't have control over actions performed in threads created in other plugins. Because of this, manually changing the culture for each thread is not a valid option.

What I am looking for is a way to set the overall language for the application, without having to change any of the OS user settings.

In doing some research, I came across the following method for setting the preferred UI language for a process: SetProcessPreferredUILanguages

After reading up on this, it appears that this call is what I am looking for. However, when I implemented this call in the Main method of my C# application, it doesn't appear to do anything.

The return value from the following code is true, but I never see my GUI application displaying the text in Spanish.

    [DllImport("Kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
    public static extern Boolean SetProcessPreferredUILanguages(UInt32 dwFlags, String pwszLanguagesBuffer, ref UInt32 pulNumLanguages);

    public void SetLanguages()
    {
        uint numLangs = 0;
        string[] langs = new string[3];
        uint MUI_LANGUAGE_NAME = 0x8; // Use ISO language (culture) name convention

        langs[0] = "es\u0000";
                langs[1] = "zh-CHS\u0000";
        langs[2] = "en-US\u0000";

        numLangs = (uint)langs.Length;

        if (SetProcessPreferredUILanguages(MUI_LANGUAGE_NAME, String.Concat(langs), ref numLangs))
        {
            Console.WriteLine("Successfully changed UI language");
        }
    }

Is there something else I am missing in order for this to successfully run my GUI application with the Spanish resources loaded?

I am trying to implement the 2nd option of the table at the bottom of the MSDN page for Building MUI Applications, where I have Application-specific UI language settings and want to achieve the desired result for resource loading:

Application calls MUI API to set application-specific UI languages or process-preferred UI languages and then calls standard resource loading functions. Resources are returned in the languages set by the application or system languages.

I have made the call to successfully set the process preferred UI languages, but my resources are not being loaded in the language I would expect. A commenter mentioned this call will only work for un-managed resources, which I could not verify 100%, but the behavior seems in indicate this is the case.

I can't be the only person who has ever tried to implement a .NET application in this manner. It's frustrating that there isn't more information about how to do this.

Thanks in advance,

Kyle

KyleK
  • 190
  • 13
  • Can you describe the problems that you found with just setting the CurrentUICulture to spanish? That is, why is that not sufficient? – Anders Forsgren Nov 29 '11 at 14:30
  • Updated my description to explain why this is not sufficient. – KyleK Nov 29 '11 at 15:42
  • Have you ruled out a) not generating translated strings in the background threads and instead generate resource keys that the ui can translate, or b) passing the required culture from the ui to the worker thread so it can return strings for the desired culture? It feels like doing translations on a worker thread using the threads "currentculture" is just wrong (although I can't really put my finger on why...) – Anders Forsgren Nov 29 '11 at 15:48
  • 1
    That function only has an effect on the resource loader for unmanaged resources, not .NET resources. There's no good way to do this, you hit the problem early but threadpool threads are the typical troublemakers. Recommend your user to buy a license to the Windows Ultimate edition so she can dynamically switch between languages. Also ensures that Windows dialogs have the proper language. – Hans Passant Nov 29 '11 at 16:30
  • @HansPassant: I updated my problem description with more research I have done on the subject. From everything I have read (unless I am missing some important details), if I set the process preferred UI languages, I would expect all resources to be loaded using the specified languages: http://msdn.microsoft.com/en-us/library/ee264324%28v=VS.85%29.aspx#building_mui_applications – KyleK Dec 02 '11 at 16:52
  • MUI is a native Windows concept. You're unconvinced, I can only suggest you observe the actual behavior. – Hans Passant Dec 02 '11 at 16:58

3 Answers3

1

Starting with .NET 4.5 the CultureInfo.DefaultThreadCurrentCulture property allows you to set the default culture for threads in the current application domain. Other than that I have not found a way to do this elegantly.

Here is the code that will show you how to reproduce this behavior:

using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

namespace SimpleMultithreadedTestForCulture {

  class Program {

    static void Main() {
      const double value = 12345.78;
      Console.WriteLine("Value from local thread - default culture: {0}", value.ToString("C"));
      CultureInfo myCulture = new CultureInfo(Thread.CurrentThread.CurrentUICulture.Name);
      myCulture.NumberFormat.CurrencySymbol = "₤";
      Thread.CurrentThread.CurrentUICulture = myCulture;
      Thread.CurrentThread.CurrentCulture = myCulture;
      Console.WriteLine("Value from local thread - 'my' culture: {0}", value.ToString("C"));
      Task.Factory.StartNew(() => Console.WriteLine("Value from a different thread: {0}", value.ToString("C")));
      Console.ReadKey();
    }

  }

}

Output:

Value from local thread - default culture: $12,345.78
Value from local thread - 'my' culture: £12,345.78
Value from a different thread: $12,345.78

Here is a related question with a possible solution for .NET 4.0 or before, using a helper class to create your threads. Even though they talk about WPF in there, it applies to other .NET multi-threaded applications too.

I know you did not want to change the culture for each of the new threads, but I cannot think of any other efficient alternatives for it.

Community
  • 1
  • 1
Daniel
  • 1,195
  • 9
  • 13
0

Your code has a few problems. Unfortunately the fix of problems do not bring the requested behaviour.

The code should be:

[DllImport("Kernel32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
public static extern Boolean SetProcessPreferredUILanguages(UInt32 dwFlags,
  String pwszLanguagesBuffer, ref UInt32 pulNumLanguages);

public void SetLanguages()
{
  uint numLangs = 0;
  string[] langs = new string[4];
  uint MUI_LANGUAGE_NAME = 0x8; // Use ISO language (culture) name convention

  langs[0] = "es\0";
  langs[1] = "zh-CHS\0";
  langs[2] = "en-US\0";
  langs[3] = "\0"; //double null ending

  numLangs = (uint)langs.Length;

  if (SetProcessPreferredUILanguages(MUI_LANGUAGE_NAME, String.Concat(langs), ref numLangs))
  {
    if (numLangs == langs.Length - 1)
      Console.WriteLine("Successfully changed UI language");
    else if (numLangs < 1)
      Console.WriteLine("No language could be set");
    else
      Console.WriteLine("Not all languages were set");
  }
  else
    Console.WriteLine("No language could be set");
}

The pwszLanguagesBuffer is defined as PCZZWSTR and that means list of wide string (therefore Charset.Unicode), string end by zero character (no need to use unicode escape sequence), list ends with double zero character (therefore new "empty language"; other way is to put double \0 to last language). I also added new debug description, to test currently set languages, but unfortunately I can not test it here (at work) because the function needs Win7.


But there is a fully different way how to do this. See AppLocale

I can not give a guarantee, that it will work in your case. The application needs Windows XP and newer (there is a small problem in installing on Windows 7; remember to start the installation in administrator mode, otherwise it would not work). The application has also another, bigger problem. It is not possible to switch from Central Europe to Western. But it works from Central Europe to Greece, and other countries with different characters.

I use this application to develop an older (non unicode) application for some countries. Unfortunately it gives a troublesome warning on start...

Julo
  • 1,102
  • 1
  • 11
  • 19
  • Besides that locale names `es` and `zh-CHS` are not interpreted correctly. You should use `es-ES` and `zh-CN` respectively (lowercase is also fine), as I described in https://stackoverflow.com/a/52103357/4454665. – SWdV Aug 30 '18 at 18:39
0

Based on feedback here, and other research, I have come to the conclusion that setting the culture for a multi-threaded .NET application independent of the OS language is not possible using .NET versions older than 4.5.

This really is a shame.

We recently upgraded our software to use .NET 4.0.

KyleK
  • 190
  • 13