Quickly Testing Code Under Different Cultures

Earlier this week, a situation came up where we needed to make sure a new feature worked when it was used with a non-English culture.  Normally we'd run some tests on a Japanese machine, but one wasn't readily available at the time.  Instead, I put together a quick tool that our tester could use to run his tests under.

The tool simply switches the current culture (and UI culture by default) of the running thread, then proceeds to execute the target application.  The obvious downside is that the tool only changes the culture of the main thread, so any work done on newly created threads or the thread pool will not automatically pick up the culture change.  However, for cases where you know that your work will only be done on one thread, or you manually propagate culture information, this will work as a quick and dirty way to launch applications.

(The usual disclaimers apply -- this code is provided as-is and if it happens to cause your monitor to explode while you run it, Microsoft is not responsible, etc, etc.)

using System;

using System.Diagnostics;

using System.IO;

using System.Globalization;

using System.Security;

using System.Threading;

 

/// <summary>

///    Tool which runs an application with the main thread set to the specified culture

/// </summary>

public static class RunLoc

{

    private static string application;

    private static string[] applicationArguments;

    private static CultureInfo culture;

    private static bool onlyUI;

    private static bool verbose;

 

    public static int Main(string[] args)

    {

        if (!ParseArguments(args))

        {

            Usage();

            return -1;

        }

 

        Debug.Assert(!String.IsNullOrEmpty(application), "!String.IsNullOrEmpty(application)");

        Debug.Assert(culture != null, "culture != null");

 

        if (verbose)

        {

            Console.WriteLine("RunLoc running on .NET version {0} ({1} bit)",

                              Environment.Version,

                              IntPtr.Size * 8);

            if (!onlyUI)

            {

                Console.WriteLine(" Current culture: {0} [replacing with {1}]",

                                  Thread.CurrentThread.CurrentCulture.DisplayName,

                                  culture.DisplayName);

            }

            Console.WriteLine(" Current UI culture: {0} [replacing with {1}]",

                              Thread.CurrentThread.CurrentUICulture.DisplayName,

                              culture.DisplayName);

            Console.WriteLine();

        }

 

        try

        {

            if (!onlyUI)

                Thread.CurrentThread.CurrentCulture = culture;

            Thread.CurrentThread.CurrentUICulture = culture;

        }

        catch (NotSupportedException)

        {

            Console.Error.WriteLine("Error: Culture {0} is not supported.", culture.DisplayName);

            return -1;

        }

        catch (SecurityException)

        {

            Console.Error.WriteLine("Error: RunLoc does not have sufficient permission to change the culture");

            return -1;

        }

 

        try

        {

            return AppDomain.CurrentDomain.ExecuteAssembly(application, null, applicationArguments);

        }

        catch (FileNotFoundException)

        {

            Console.Error.WriteLine("Error: '{0}' could not be found", application);

            return -1;

        }

        catch (SecurityException)

        {

            Console.Error.WriteLine("Error: RunLoc does not have sufficient permission to run the application");

            return -1;

        }

    }

 

    private static bool ParseArguments(string[] arguments)

    {

        Debug.Assert(arguments != null);

 

        if (arguments.Length < 2)

            return false;

 

        // All of the RunLoc arguments must come before the application name, since that is just followed

        // by its own arguments. Loop until we hit the last argument, or until we find the application name.

        int currentArgument = 0;

        while (currentArgument < arguments.Length && application == null)

        {

            if (String.Equals(arguments[currentArgument], "/v", StringComparison.OrdinalIgnoreCase))

            {

                verbose = true;

            }

            else if (String.Equals(arguments[currentArgument], "/onlyui", StringComparison.OrdinalIgnoreCase))

            {

                onlyUI = true;

            }

            else if (culture == null)

            {

                try

                {

                    culture = CultureInfo.GetCultureInfo(arguments[currentArgument]);

                }

                catch (ArgumentException)

                {

                    Console.Error.WriteLine("Error: culture '{0}' is not found", arguments[currentArgument]);

                    return false;

                }

            }

            else

            {

                application = arguments[currentArgument];

            }

 

            currentArgument++;

        }

 

        // Make sure our required parameters are present

        if (culture == null)

        {

            Console.Error.WriteLine("Error: No culture given.");

            return false;

        }

        if (String.IsNullOrEmpty(application))

        {

            Console.Error.WriteLine("Error: No application given.");

            return false;

        }

 

        // Any remaining arguments belong to the application

        if (currentArgument < arguments.Length - 1)

        {

            applicationArguments = new string[arguments.Length - currentArgument];

            Array.Copy(arguments, currentArgument, applicationArguments, 0, applicationArguments.Length);

        }

 

        return true;

    }

 

    private static void Usage()

    {

        Console.WriteLine("Usage: RunLoc [/v] [/onlyui] culture application [application arguments ...]");

        Console.WriteLine();

        Console.WriteLine(" /v - verbose mode");

        Console.WriteLine(" /onlyui - only change the UI culture");

        Console.WriteLine(" culture - culture to set the main thread to");

        Console.WriteLine(" application - path to the application to run");

        Console.WriteLine(" application arguments - arguments for the application");

        Console.WriteLine();

    }

}

Using this is pretty easy:

C:\RunLoc>FloridaBreakfastFruit.exe
Mmm ... grapefruit

C:\RunLoc>RunLoc /v fr-fr FloridaBreakfastFruit.exe
RunLoc running on .NET version 2.0.50727.42 (64 bit)
  Current culture:    English (United States) [replacing with French (France)]
  Current UI culture: English (United States) [replacing with French (France)]

Mmm ... pamplemousse