Globally overrride MonthNames for all instances of a specific culture
Asked Answered
C

6

6

So, i have this problem where Microsoft actually got the month names wrong for the Greenlandic culture (kl-GL). I also know that i can pass my own array of string to the DateTimeFormatInfo.MonthNames Property, but it seems like the values i specify is only used in the scope of that one CultureInfo instance. Is there a way to tell .Net that every time i have an instance of the kl-GL culture these specific monthnames should be used?

I know that you can create user specific cultures, but i don't have access to some legacy code to actually change the code to use a my own userspecified culture.

Colorfast answered 17/12, 2010 at 19:31 Comment(3)
That is an amazing bug. Have you searched/logged that on connect?Mucoid
thats what happens when a language evolves... the problems is with January and February where the correct spelling is Januaari and Februaari where .Net returns Januari and Februari (an a is missing). yes, its filed on Connect but that can take years and years before anything happens.Nollie
and to prove my point, this is the official translations of the Month Names from Danish to Greenlandic service.oqaasileriffik.gl/cgi-bin/…Nollie
S
1

Here you go

    public static void RenameMonthNames(string cultureName, string[] newNames)
    {
        RenameMonthNames(cultureName, newNames, false);
        RenameMonthNames(cultureName, newNames, true);
    }


    public static void RenameMonthNames(string cultureName, string[] newNames, bool custom)
    {
        var nonPublicAndInstance = BindingFlags.NonPublic | BindingFlags.Instance;

        var culture = new CultureInfo(cultureName, custom);

        int calendarId = (int)typeof (System.Globalization.Calendar).GetProperty("ID", nonPublicAndInstance).GetValue(culture.Calendar, new object[0]);

        object cultureData = culture.GetType().GetField("m_cultureData", nonPublicAndInstance).GetValue(culture);

        cultureData.GetType().GetField("bUseOverrides", nonPublicAndInstance).SetValue(cultureData, false); // Magic hack!!!

        object calendarData = cultureData.GetType().GetMethod("GetCalendar", nonPublicAndInstance).Invoke(cultureData, new object[] { calendarId });

        calendarData.GetType().GetField("saMonthNames", nonPublicAndInstance).SetValue(calendarData, newNames);
        calendarData.GetType().GetField("saLeapYearMonthNames", nonPublicAndInstance).SetValue(calendarData, newNames);
        calendarData.GetType().GetField("saMonthGenitiveNames", nonPublicAndInstance).SetValue(calendarData, newNames);
    }

    public  void TestCultureInfoHack()
    {
        RenameMonthNames("da-DK", new string[]
                                      {
                                          "jan1", "feb2", "mar3", "apr", "may", "jun",
                                          "jul", "aug", "sep", "okt", "nov", "dec12", string.Empty
                                      });

        var today = DateTime.Now.ToLongDateString();
        Thread.CurrentThread.CurrentCulture = new CultureInfo("kl-gl", false);
        Response.Write(DateTime.Now.ToLongDateString());

        Response.Write("<br /> "); 

        Thread.CurrentThread.CurrentCulture = new CultureInfo("kl-GL");
        Response.Write(DateTime.Now.ToLongDateString());
        Response.Write("<br /> "); 
    }

NOTE: only for .NET 4.0

Subjunctive answered 21/12, 2010 at 21:41 Comment(6)
actually its very easy to set new MonthNames when you have a reference to a CultureInfo object. My problem is, that setting the MontNames on that one reference, doesn't seem to have any effect NEXT time i create an instance of kl-GL, or for whoever else is creating instances of kl-GL.Nollie
I do not understand why you haven't accepted my answer, accorning to you you need: "i guess what i was hoping for was that i somewhere in my application startup could override/define the monthnames for every future instance of kl-GL being instantiated in the scope of that application." I tested, and that's exactly what my code does, I even added a piece of code that illustrates it. I guess it is either: a) You don't believe it is working (lol ???) b) You have something personal agains me :) Either way, why haven't you tried it?Subjunctive
No need to get personal here :) I've been busy, its been Christmas and all so i haven't been able to test it before now! It seems to be... working though :) I had some problems at first, but testing shows that the CultureInfo instance that we're working with HAS be to created with useUserOverride set to true, otherwise every new instance won't pick up my month names.Nollie
and im sorry i misunderstood your first answer before you pasted some code as well. I didn't get a chance to look at it before now, so its not because i didn't believe it would work or because i have anything against you :PNollie
in TestCultureInfoHack() I tested both with "false" and "true", and I worked (at least once, and on my machine). Could it be the case that you're testing it with a CultureInfo instance which was created before the RenameMonthNames(...) was called?Subjunctive
im calling the code in the Init-method of HttpApplication so it should be as early as possible. I'm not sure what effect it has to "hack" the useUserOverride instance (except that it works). Everywhere else my legacy code requires a CultureInfo object its using the method CultureInfo.GetCultureInfo(string) and not new CultureInfo(string)Nollie
C
2

Why do you need more than one or two CultureInfo object? You can change the threading culture (System.Threading.Thread.CurrentThread.CurrentCulture) or the threading UI culture (System.Threading.Thread.CurrentThread.CurrentUICulture) to the values you need. Then you can use current culture or refer to the objects in CurrentThread if you need an explicit reference.

Clamper answered 22/12, 2010 at 14:23 Comment(2)
yes, i have been looking into this, but since i'm using asp.net (added the tag to the question), the recommended way of setting current culture is in the InitializeCulture() method on Page where you set the Page.Culture property. The annoying thing though is that it only takes a string paramater, internally calling HttpServerUtility.CreateReadOnlyCultureInfo from that string, i can't pass my own custom modified object.Nollie
In ASP.NET it's the recommended way to set the pages culture by setting the threading culture in InitializeCulture. Look here msdn.microsoft.com/en-us/library/bz9tc508.aspxClamper
D
1

Replacing a specific culture.

var cultureBuilder = new CultureAndRegionInfoBuilder(
               "kl-GL", CultureAndRegionModifiers.Replacement);

cultureBuilder.LoadDataFromCultureInfo(new CultureInfo("kl-GL"));

cultureBuilder.GregorianDateTimeFormat.MonthNames = new []
                       {
                            "jan", "feb", "mar", "apr", "may", "jun",
                            "jul", "aug", "sep", "okt", "nov", "dec",
                            string.Empty    // needs to be here!!!
                       };

cultureBuilder.Register();

Don't execute this! It will overwrite your culture settings. Adjust as you need it.

Creating a new specific culture.

    static void Main(string[] args)
    {
        try
        {
           var builder = new CultureAndRegionInfoBuilder(
                "kl-GL-custom",
                CultureAndRegionModifiers.None);

            // bind the properties
            builder.LoadDataFromCultureInfo(new CultureInfo("kl-GL"));
            builder.LoadDataFromRegionInfo(new RegionInfo("kl-GL"));

            // make custom changes to the culture
            builder.GregorianDateTimeFormat.MonthNames = new []
                {
                    "jan", "feb", "mar", "apr", "may", "jun",
                    "jul", "aug", "sep", "okt", "nov", "dec",
                    string.Empty    // needs to be here!!!
                };

            // one time operation! needs admin rights!
            builder.Register();
        }
        catch
        {
        }

        Thread.CurrentThread.CurrentCulture = 
            Thread.CurrentThread.CurrentUICulture = 
                new CultureInfo("kl-GL-custom");

        Console.WriteLine(DateTime.Today.ToString("MMMM"));

    }

Using the new culture in ASP.NET is as trivial as adding this to your web.config:

<globalization culture="kl-GL-custom" uiCulture="kl-GL-custom"/>

or do this

    protected override void InitializeCulture()
    {
        Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = new CultureInfo("kl-GL-custom");

        base.InitializeCulture();
    }
Dynamite answered 21/12, 2010 at 18:53 Comment(6)
will this actually create a new custom culture and save it under %windir%\Globalization !? I am aware of this method, which i refers to as user specific cultures in my question and was hoping for a solution that didn't involved it.Nollie
Yes, this saves a NLP file in %windir%\Globalization. Could you explain a bit more clearly what you're after?Dynamite
i guess what i was hoping for was that i somewhere in my application startup could override/define the monthnames for every future instance of kl-GL being instantiated in the scope of that application. Creating my own custom culture is maybe not such a bad idea after all though, i just need to figure out how to get it installed on the webserver.Nollie
The first piece of code will replace the kl-GL culture. I would however advise to create a new culture derived from the existing one. see my updated answer.Dynamite
lets say i was to name the custom culture kl-GL to override the existing one, will that even have effect on ie. Outlook on my computer showing correct month names in the calendar as well?Nollie
I'm pretty sure it won't affect Outlook or IE. Outlook comes with Office and office comes with a baked in language like EN, FR, etc..., same for IE. I'm running an English version of Office while the locale on my pc is nl-be and everything is displayed in English (I'm talking about the UI, not spelling check functionality, etc...). Any program using .Net's CultureInfo to display dates, currency, etc... will of course be affected.Dynamite
S
1

Here you go

    public static void RenameMonthNames(string cultureName, string[] newNames)
    {
        RenameMonthNames(cultureName, newNames, false);
        RenameMonthNames(cultureName, newNames, true);
    }


    public static void RenameMonthNames(string cultureName, string[] newNames, bool custom)
    {
        var nonPublicAndInstance = BindingFlags.NonPublic | BindingFlags.Instance;

        var culture = new CultureInfo(cultureName, custom);

        int calendarId = (int)typeof (System.Globalization.Calendar).GetProperty("ID", nonPublicAndInstance).GetValue(culture.Calendar, new object[0]);

        object cultureData = culture.GetType().GetField("m_cultureData", nonPublicAndInstance).GetValue(culture);

        cultureData.GetType().GetField("bUseOverrides", nonPublicAndInstance).SetValue(cultureData, false); // Magic hack!!!

        object calendarData = cultureData.GetType().GetMethod("GetCalendar", nonPublicAndInstance).Invoke(cultureData, new object[] { calendarId });

        calendarData.GetType().GetField("saMonthNames", nonPublicAndInstance).SetValue(calendarData, newNames);
        calendarData.GetType().GetField("saLeapYearMonthNames", nonPublicAndInstance).SetValue(calendarData, newNames);
        calendarData.GetType().GetField("saMonthGenitiveNames", nonPublicAndInstance).SetValue(calendarData, newNames);
    }

    public  void TestCultureInfoHack()
    {
        RenameMonthNames("da-DK", new string[]
                                      {
                                          "jan1", "feb2", "mar3", "apr", "may", "jun",
                                          "jul", "aug", "sep", "okt", "nov", "dec12", string.Empty
                                      });

        var today = DateTime.Now.ToLongDateString();
        Thread.CurrentThread.CurrentCulture = new CultureInfo("kl-gl", false);
        Response.Write(DateTime.Now.ToLongDateString());

        Response.Write("<br /> "); 

        Thread.CurrentThread.CurrentCulture = new CultureInfo("kl-GL");
        Response.Write(DateTime.Now.ToLongDateString());
        Response.Write("<br /> "); 
    }

NOTE: only for .NET 4.0

Subjunctive answered 21/12, 2010 at 21:41 Comment(6)
actually its very easy to set new MonthNames when you have a reference to a CultureInfo object. My problem is, that setting the MontNames on that one reference, doesn't seem to have any effect NEXT time i create an instance of kl-GL, or for whoever else is creating instances of kl-GL.Nollie
I do not understand why you haven't accepted my answer, accorning to you you need: "i guess what i was hoping for was that i somewhere in my application startup could override/define the monthnames for every future instance of kl-GL being instantiated in the scope of that application." I tested, and that's exactly what my code does, I even added a piece of code that illustrates it. I guess it is either: a) You don't believe it is working (lol ???) b) You have something personal agains me :) Either way, why haven't you tried it?Subjunctive
No need to get personal here :) I've been busy, its been Christmas and all so i haven't been able to test it before now! It seems to be... working though :) I had some problems at first, but testing shows that the CultureInfo instance that we're working with HAS be to created with useUserOverride set to true, otherwise every new instance won't pick up my month names.Nollie
and im sorry i misunderstood your first answer before you pasted some code as well. I didn't get a chance to look at it before now, so its not because i didn't believe it would work or because i have anything against you :PNollie
in TestCultureInfoHack() I tested both with "false" and "true", and I worked (at least once, and on my machine). Could it be the case that you're testing it with a CultureInfo instance which was created before the RenameMonthNames(...) was called?Subjunctive
im calling the code in the Init-method of HttpApplication so it should be as early as possible. I'm not sure what effect it has to "hack" the useUserOverride instance (except that it works). Everywhere else my legacy code requires a CultureInfo object its using the method CultureInfo.GetCultureInfo(string) and not new CultureInfo(string)Nollie
I
1

I think @Dirk's answer is best. On this MSDN page, it explains how to initialize the culture for an ASP.NET page:

  1. Override the InitializeCulture method for the page.
    1. In the overridden method, determine which language and culture to set the page to.
    2. Set the UI culture and culture in one of the following ways:
      • Set the Culture and UICulture properties of the page to the language and culture string (for example, en-US). These properties are internal to the page, and can only be used in a page.
      • Set the CurrentUICulture and CurrentCulture properties of the current thread to the UI culture and culture, respectively. The CurrentUICulture property takes a language and culture information string. To set the CurrentCulture property, you create an instance of the CultureInfo class and call its CreateSpecificCulture method.

Example:

public class YourCodeBehind : Page
{
    protected override void InitializeCulture()
    {
        CultureInfo cultureInfo = new CultureInfo("kl-GL");
        cultureInfo.DateTimeFormat.MonthNames = new string[] { ... };
        Thread.CurrentThread.CurrentCulture = cultureInfo;
    }
}
Inimical answered 22/12, 2010 at 11:47 Comment(3)
like i said, there is legacy code i can't change which relies on the CultureInfo object being set automatically by the asp.net runtime via its <globalization> element in web.config. I have been trying to override Page.Culture in the InitializeCulture method, but it only takes a string as parameter and internally calls HttpServerUtility.CreateReadOnlyCultureInfo() so i can't pass my own modified CultureInfo object.Nollie
I'm sorry -- I misunderstood that. That certainly does complicate things. Do you have a sample of what the legacy code actually looks like? Does it refer directly to the Page.Culture property?Inimical
I completely changed my answer. I found some useful info on the MSDN page. Inside the InitializeCulture method, they recommend either setting Page.Culture/UICulture (the string value) OR setting the CurrentCulture/CurrentUICulture properties of the Thread.CurrentThread. So, the point is this: you can specify a complete CultureInfo object, not just the locale string.Inimical
C
0

This is completely a hack/brute force answer, but you could handle the Page_PreRender event, detect the culture, and then and do a string replace on those words before they are sent out to the browser.

I have no idea what the performance implication of this is.

Cypress answered 22/12, 2010 at 16:45 Comment(0)
S
0

You may want to investigate the mocking framework TypeMock Isolator. With it you can override many things in the CLR, including what gets returned from a "new" invocation. So if you were to set up a "fake" CultureInfo with the corrected month names close to your program's entry point, each call to new CultureInfo("kl-GL") would return your corrected instance.

The performance implications are not great (TypeMock apparently works via the CLR's profiling hooks), but it is worth investigating if the benefits of correctness outweigh a small performance penalty.

Slipnoose answered 22/12, 2010 at 17:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.