44

How to get the resx file strings in asp.net core? I get the strings using ResxResourceReader in mvc. But I can't get the same in asp.net core.

1
  • 2
    I want to access the entries in resx file as keyvalue pair. How to do this in dotnet core Commented Apr 4, 2017 at 5:56

3 Answers 3

61

.NET Core changed how resource files work in a way I feel is sub-par and confusing (took me days to figure out), but this is what you need to do:

  1. Add the following code to Startup.cs - Note: Change what languages you support and the ResourcePath of "Resources" will just be the folder you store the .resx files later.

As JustAMartin said in comments: If you are planning to put your SharedResource file inside Resources folder and set its namespace to end with Resources then do not set o.ResourcesPath = "Resources" just use services.AddLocalization(), otherwise it will start looking in Resources.Resources folder, which doesn't exist.

        services.AddLocalization(o => o.ResourcesPath = "Resources");
        services.Configure<RequestLocalizationOptions>(options =>
        {
            var supportedCultures = new[]
            {
                new CultureInfo("en-US"),
                new CultureInfo("en-GB"),
                new CultureInfo("de-DE")
            };
            options.DefaultRequestCulture = new RequestCulture("en-US", "en-US");

            // You must explicitly state which cultures your application supports.
            // These are the cultures the app supports for formatting 
            // numbers, dates, etc.

            options.SupportedCultures = supportedCultures;

            // These are the cultures the app supports for UI strings, 
            // i.e. we have localized resources for.

            options.SupportedUICultures = supportedCultures;
        });
  1. Create a folder in whatever project you want to store the resx files in - default, call it "Resources".

  2. Create a new resx file with the specific culture and the file name you'll look up later: If you had a shared one, you could do: SharedResource.en-US.resx. Then turn off auto-code generation as it is useless now.

  3. Create a class called "SharedResource" in the same location as your resx file. It can be blank, it just needs to be there so you can reference it later.

  4. Wherever you want to use your resource, IoC inject (in this example) IStringLocalizer< SharedResource > with name "_localizer" or something.

  5. Finally, you can reference an entry in the Resource file by doing _localizer["My_Resource_Name"]

  6. Add another language by creating a new resx file named "SharedResource.de-DE.resx" or whatever, in that same folder.

The "Resource" folder will be used across all assemblies to look all of them up. Thus, this folder could end up pretty cluttered, especially if you start getting view specific stuff in here.

I see what the devs were trying to do here, but they gave up too much to get there. People can code and add translation stuff without actually translating anything. They made it easier for devs to have translation in mind from the start, but they end up making it way more work for the devs that actually use translations. Now we can't auto generate anything. We have to IoC inject a reference to the translations in order to access them (no more static unless you want to use the ServiceLocater anti-pattern). All the names are hard-coded strings, so now if you spell a translation wrong it'll just spit back the string you gave it, defeating the purpose of having a translation in the first place, meaning you'll probably need a wrapper around this so you don't rely on constants everywhere.

I can't believe anyone thought this was a good idea, to be honest. Why bend over backwards for devs that don't care about translations, anyway?

I ended up creating a wrapper around this style. The only good thing about this is that if you decide you want to get resources from the database, no code change above will be necessary, but now you have to add the resource entry, add it to the interface, and then implement it to pull it back out again. I used nameof() so I didn't need to use constants, but this is still brittle as if the property name or resx file name changes, it'll break the translation without any sort of crash - I will probably need an integration test to ensure I don't get the same value I send in:

public interface ICommonResource
{
    string ErrorUnexpectedNumberOfRowsSaved { get; }
    string ErrorNoRecordsSaved { get; }
    string ErrorConcurrency { get; }
    string ErrorGeneric { get; }

    string RuleAlreadyInUse { get; }
    string RuleDoesNotExist { get; }
    string RuleInvalid { get; }
    string RuleMaxLength { get; }
    string RuleRequired { get; }
}

public class CommonResource : ICommonResource
{
    private readonly IStringLocalizer<CommonResource> _localizer;

    public CommonResource(IStringLocalizer<CommonResource> localizer) =>
        _localizer = localizer;

    public string ErrorUnexpectedNumberOfRowsSaved => GetString(nameof(ErrorUnexpectedNumberOfRowsSaved));
    public string ErrorNoRecordsSaved => GetString(nameof(ErrorNoRecordsSaved));
    public string ErrorConcurrency => GetString(nameof(ErrorConcurrency));
    public string ErrorGeneric => GetString(nameof(ErrorGeneric));

    public string RuleAlreadyInUse => GetString(nameof(RuleAlreadyInUse));
    public string RuleDoesNotExist => GetString(nameof(RuleDoesNotExist));
    public string RuleInvalid => GetString(nameof(RuleInvalid));
    public string RuleMaxLength => GetString(nameof(RuleMaxLength));
    public string RuleRequired => GetString(nameof(RuleRequired));

    private string GetString(string name) =>
        _localizer[name];
}
Sign up to request clarification or add additional context in comments.

11 Comments

I believe that you can still use the ResXFileCodeGenerator to generate compile-time props (as seen here for example: christianspecht.de/2017/11/29/… ).
I did but then the culture change was ignored for me.
I found a workaround. I'm using a "BaseController" and two resx files: "Controllers.BaseController.resx" and "Controllers.BaseController.fr.resx". This class in inherited by my "true" controllers and I pass a "Text" var which contains the "IStringLocalizer" object with the translated string to the models (again, I use a "BaseModel" for this). This is quite ugly, but this is as close as I wanted. I also use a Constant class to not deal with the string keys, I hate that and we can always mistype something. I may write a blog post one day to explain this in a proper way.
@Mayako It looks like you need to call this instead in ASP.NET Core 2: services.AddLocalization(); - I know it's probably too late for you but it might help others :)
Beware of two "gotchas": 1) IStringLocalizer doesn't like short culture names. It just didn't work with lv although current thread cultures and resource name was lv. It started to work when I renamed them all to lv-LV. 2) if you put your SharedResource file inside Resources folder and set its namespace to end with Resources then do not set o.ResourcesPath = "Resources" because then it will start looking in Resources.Resources folder, which doesn't exist. So, it's either namespace or ResourcesPath but not both.
|
7

The old way version (like in asp.net) is to create a default resource file like MyResources.resx and other files for different cultures MyResources.fr.resx, ... and retrieve the values from it with MyResources.MyValue1 . Creating MyResources.resx will generate a .cs file with all your resource values as static properties.

.Net Core recommends to work with IStringLocalizer<T> where T is a class created by you that match with the name of your resource files. You can start development without any resource file and add them later. You have to inject (IStringLocalizer< MyResources > localizer) on your controller and than get the value with _localizer["MyValue1"];

You can take a look over the .net core official documentation here

13 Comments

Why would they do that? Now we have to use strings that will let you get run-time crashes...
@DanielLorenz: If X is undefined, usually localizer[X] == X . So you usually won't get run-time crashes. If your internal policy is that localizer[X] == X should be true for your default culture, this gives pretty good results. A further benefit of this approach is that it forces you to be explicit about whether you want a string localizer or an html localizer. That said, the real reason MVC took this approach is because MVC uses dependency injection for everything.
Beyond string vs. html, the only real-world case I can think of for wanting to use dependency injection for your localizer is to inject a custom, "I forgot to localize" string during debugging. If you replace your unlocalized strings with similar strings (e.g., add an accent mark over most of your characters), spotting raw strings becomes easier.
@DanielLorenz: I agree with you; I prefer the designer approach (which still works in .Net Core). The cost you pay for statically defining every resource string is that your resource class is tightly coupled with your application. That cost is incompatible with Microsoft's attempt to avoid all dependencies in favor of modular, injectable components. In the case of third-party libraries, avoiding coupling has substantial merit. In the case of application-specific localization, your translation is inherently coupled to your application, so tight coupling has minimal downsides.
The issue I have with the Microsoft implementation is that it's worse for people who need internationalization and isn't useful for people who don't need internationalization. Making even a slight correction to a string will lose you your translations. Much better to have the tightly coupled Resources files.
|
6

For a more direct replacement, I have created ResXResourceReader.NetStandard, which repackages ResXResourceReader and ResXResourceWriter for .NET Standard (which means .NET Core too).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.