0

I have a system where localization can very easily vary at a per-customer level. We use cultures like

fr-MYCLIENT

These cultures get registered with the following code:

private List<MyBaseCulture> supportedCultures = new List<MyBaseCulture>
{
    { new MyBaseCulture("pt-PT") },
    { new MyBaseCulture("en-US") },
    { new MyBaseCulture("fr-FR") },
    { new MyBaseCulture("nl-NL") },
    { new MyBaseCulture("es-ES") },
    { new MyBaseCulture("ca-ES") },
    { new MyBaseCulture("cs-CZ") },
};
public string RegisterCulture(string parentCulture, string newCultureName)
{
    MyBaseCulturemyParent = supportedCultures.Where(x => x.Equals(new MyBaseCulture(parentCulture))).FirstOrDefault();

    if(myParent==null)
    {
       throw new Exception($"Unsupported culture requested during culture registration: '{parentCulture}'");
        }

    CultureInfo ci = new CultureInfo(myParent.Culture);
    RegionInfo ri = new RegionInfo(myParent.Region)
    CultureAndRegionInfoBuilder myBuilder = new CultureAndRegionInfoBuilder(newCultureName, CultureAndRegionModifiers.None);
    myBuilder.LoadDataFromCultureInfo(ci);
    myBuilder.LoadDataFromRegionInfo(ri);

    bool cultureAlreadyExists = DoesCultureExist(parentCulture);

    try
    { 
        if(cultureAlreadyExists)
            CultureAndRegionInfoBuilder.Unregister(newCultureName);
        myBuilder.Register();
    }
    catch(Exception e)
    {
        return "Exception during culture registration. Details: " + e.Message;
    }
    return cultureAlreadyExists ? 
          "Successfully registered culture " + parentCulture
          : "Culture failed to register...";
}

an invocation of this would look like

new CultureRegistration().RegisterCulture("es-ES", "es-CLIENT01");

I am using RESX files to keep track of this. The files use the custom tool GlobalResourceProxyGenerator, responsible for creating designer classes for all resource entries for quick class access.

The issue is that when I attempt to build my project in a machine without the custom cultures registered.

As long as the machine i'm using to compile code is missing at least one of my custom cultures, I get a compilation error like thus:

Exception text

Compiler Error Message: CS0101: The namespace 'Resources' already contains a definition for 'ChartResources'
Source File: c:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\vs\fc87251a\66d5efac\App_GlobalResources.zetsmdoe.4.cs    Line: 26 

This ends up being very cumbersome as we need an external tool to register cultures just to be able to compile our code every time we need to move to a new machine or one of our team's members adds a new culture.

Is there a way to either:

  • a) run code before this compilation step (something akin to Application_Start in global.asax, but that executes before the step that throws this error)?
  • b) skip this validation unless the relevant culture is called?

The reason we use custom cultures is to store field values (such as dropdown options). Obviously, we have a considerably larger amount of installations than cultures (ie, a lot more than a single en-UK customer). Changing the values each deployment is suboptimal, as upgrading the original customer becomes a gigantic pain (ie, if we modify en-UK dropdown_1 to "Sample Company 1", then another customer requests "Example Company 2" in that same dropdown, if we are to ever upgrade the version of the first client, we would have to restore the original value. Using custom regions allows us to simply set a different region for each client, making it clear that that set of resources is specific to them.

Edit: Added an example of the culture names we use.

Edit2: Clarifying why we're using custom cultures.

Community
  • 1
  • 1
Cloud
  • 1
  • 7
  • That error has nothing to do with custom cultures. It complains that `ChartResources` is defined twice. – Panagiotis Kanavos Nov 14 '19 at 15:25
  • As for creating custom cultures, why do it in the first place? Resources change, but the locales and cultures *don't*. Unless you want different day, month names or long/short date strings for different customers in the same country ? – Panagiotis Kanavos Nov 14 '19 at 15:30
  • The error _looks_ unrelated, but it is what loading my web application shows. If I register the missing cultures **with a different site** (read: changing absolutely nothing in my code, but registering the intended cultures through a completely separate application), that compilation error disappears and everything loads as normal. While I'm not sure about _why_ that is the error that displays, I am certain it's because I'm missing the custom cultures I need. – Cloud Nov 14 '19 at 15:42
  • The error is about a *resources* class, not cultures. `GlobalResourceProxyGenerator` generates code for *resource files*, not cultures. Cultures are what `CultureInfo` represents. In the very rare case you need a custom one, you build it with [CultureAndRegionInfoBuilder](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureandregioninfobuilder?view=netframework-4.8), not resx files. Creating a custom culture *doesn't* generate resource files or classes. Are you using `culture` to refer to the resource files perhaps? – Panagiotis Kanavos Nov 14 '19 at 15:48
  • I am not, I do mean creating a culture. The code the creates the culture does use `CultureAndRegionInfoBuilder`. [Additionally, I've edited in the reason we're using multiple resources. I'll also edit the question to make it clear how I'm adding the custom cultures.] – Cloud Nov 14 '19 at 15:51
  • It's resource files that change per client, not cultures. When you run your appliction, Visual Studio will use `GlobalResourceProxyGenerator` to generate a proxy class from the `resx` file that allows easy access to the resource content. If you deploy your web application *without* the compiled proxy class, the web server may try to rebuild it from the `resx` file. – Panagiotis Kanavos Nov 14 '19 at 15:51
  • Post your code then, although *that* error and `GlobalResourceProxyGenerator` have nothing to do with custom cultures. – Panagiotis Kanavos Nov 14 '19 at 15:52
  • What probably happens is that for whatever reason, you deploy two different `ChartResources`. Perhaps a default compiled proxy class *and* a localized resx file? When your web app starts, it sees it needs the new file with an unkown culture string (*why* did you create a custom culture again????), tries to compile the file and ends up with *two* classes for the same thing in the same namespace – Panagiotis Kanavos Nov 14 '19 at 15:54
  • If you used localized `resx` files in Visual Studio, the generated files would be partial classes that would be merged during compilation. If you deploy a compiled default proxy class though, it's not possible to merge the source with the file generated from the production resx. – Panagiotis Kanavos Nov 14 '19 at 15:57
  • 1
    Again and again, why the custom cultures? People are using localized resources for 16 years without them. In fact, the ability to create custom cultures came very late, to handle cultures that aren't provided by the OS. They were *never* needed for localization. Not even for Canada (couldn't help myself) – Panagiotis Kanavos Nov 14 '19 at 15:58
  • The code you posted doesn't create new cultures. It clones existing ones. It doesn't offer anything unless you use the *names* in a way they weren't intended to be used - as keys to the resource files. Why not use the *original* names for those localized resources? You don't deploy the files for *all* clients on the same server do you? – Panagiotis Kanavos Nov 14 '19 at 16:01
  • If you *do*, you're probably building a multi-tenant application. In that case the solution is to *not* use the proxy classes because you *don't* use the culture as a key. You use both the culture and the tenant ID. You have to either create your own proxy classes or customize the template to use your own resource key logic. I'm pretty sure someone has already done this. The localization middleware doesn't require resx files to work. Any provider that implements the proper interfaces will work. – Panagiotis Kanavos Nov 14 '19 at 16:04
  • I've added the code that registers the culture (albeit not everything, hopefully the names are self-explanatory enough) and a paragraph about why we're using them. I understand the confusion, of course - I understand that the error **does** indicate a duplicated class, what I don't understand is how I can change whether or not that exception shows up by registering or unregistering cultures **completely outside of my application**. This means no code has changed in any way... Which also means there's something about the way the resource classes are generated that I do not comprehend fully. – Cloud Nov 14 '19 at 16:05
  • googling for `asp.net localization multi-tenant` I found [this question](https://stackoverflow.com/questions/6571481/localization-i18n-in-asp-net-multi-tenant-saas-application) from 2011. One of the options is to use a provider that loads localized resources from the database. – Panagiotis Kanavos Nov 14 '19 at 16:06
  • Chekc [Extending the ASP.NET 2.0 Resource-Provider Model](https://learn.microsoft.com/en-us/previous-versions/dotnet/articles/aa905797(v=msdn.10)?redirectedfrom=MSDN). That's what actually loads resources. The `GlobalResourceProxyGenerator` tool generates IResourceProvider implementations from the resx files, using the culture name as part of the key. Even with the current resx files you need to create a provider that picks the correct one using your logic. The proxy class code is a good starting point – Panagiotis Kanavos Nov 14 '19 at 16:10
  • It isn't a multi-tenant application. As for "The code you posted doesn't create new cultures. It clones existing ones. It doesn't offer anything unless you use the names in a way they weren't intended to be used - as keys to the resource files", that would be it. The problem, however, is that 2 clients from en-UK (example) _can_, and _do_, have different resources. If 2 clients have different resources but both are en-UK, I have to either use custom cultures, or not use RESX files at all, is that not the case? – Cloud Nov 14 '19 at 16:10
  • You'd never have any problems unless you deployed the resources for *both* customers on *both* companies. Which I hope you *don't* do. It's probably a breach of contract or GDPR or something - you're telling one customer who the other customers are. There's no reason to create *two* cultures. Just use `en-UK` in both cases, with different resx files – Panagiotis Kanavos Nov 14 '19 at 16:11
  • Upgrading the software becomes a nightmare in that case. We don't deploy just once and forget it - we upgrade clients when new features get implemented as part of some contracts. Managing that under a single culture requires constantly going back and forth, or to use database resources instead of RESX. – Cloud Nov 14 '19 at 16:13
  • No, it requires using the *correct* provider code for *your* scenario. How *do* you want to deploy changes? Write code that does that. The `IResourceProvider` interface won't change. `meta:resourcekey="labHelloLocalResource1"` or `<%$ Resources:CommonTerms, Hello %>` will still work. – Panagiotis Kanavos Nov 14 '19 at 16:20
  • Keep in mind that *big* applications like ERPs, stock broker apps etc work all over the world for decades. – Panagiotis Kanavos Nov 14 '19 at 16:21
  • I'll look into the DBResourceProvider, it looks to fit my circumstances far better. It wasn't my decision to make use of resx files - I don't think that fits what we want to go for due to the exact issue that clients want text that's too customized to represent as a culture - it's not what they're for. I'll see if I can understand why the exception shows up. "When your web app starts, it sees it needs the new file with an unkown culture string" seems like a logical idea, I'll see if I can find out if that's the case as I implement my provider. – Cloud Nov 14 '19 at 16:34

0 Answers0