且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在ASP.NET Core中获取.resx文件字符串

更新时间:2023-02-17 08:50:56

.NET Core改变了资源文件的工作方式,使我感觉不到标准且令人困惑(花了我几天的时间弄清楚),但这就是你所需要的.需要做的:

.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. 将以下代码添加到Startup.cs中-注意:更改支持的语言,"Resources"的ResourcePath只是以后存储.resx文件的文件夹.

JustAMartin 在评论中说:如果您打算将 SharedResource 文件放入其中 Resources 文件夹,并将其命名空间设置为以Resources结尾,然后不要设置 o.ResourcesPath = "Resources" ,只需使用 services.AddLocalization() ,否则它将开始查找Resources.Resources文件夹,该文件夹不存在.

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. 在要存储resx文件的任何项目中创建一个文件夹-默认情况下,将其称为资源".

  1. Create a folder in whatever project you want to store the resx files in - default, call it "Resources".

使用特定的区域性和文件名创建一个新的resx文件,您将在以后查找该文件名:如果您拥有共享文件,则可以执行以下操作:SharedResource.en-US.resx.然后关闭自动代码生成功能,因为它现在已经没有用了.

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.

在与resx文件相同的位置中创建一个名为"SharedResource"的类.它可以是空白,只需要放在那里即可,以便稍后使用.

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.

无论何时要使用资源,IoC注入(在此示例中)都是IStringLocalizer<名称为"_localizer"或其他名称的SharedResource>.

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

最后,您可以通过执行_localizer ["My_Resource_Name"]

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

在同一文件夹中创建一个名为"SharedResource.de-DE.resx"或其他名称的新resx文件,以添加另一种语言.

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.

我知道开发人员在这里想要做什么,但是他们放弃了太多去那里.人们可以编写代码并添加翻译内容,而无需实际翻译任何内容.从一开始,它们使开发人员更容易考虑翻译,但最终使实际使用翻译的开发人员可以进行更多工作.现在我们无法自动生成任何东西.为了访问它们,我们必须IoC注入对翻译的引用(除非您想使用ServiceLocater反模式,否则不再是静态的).所有名称都是硬编码的字符串,因此,如果您将翻译拼写错误,它只会吐回您给它的字符串,从而打败了首先进行翻译的目的,这意味着您可能需要包装纸这样,您就不会在任何地方都依赖常量.

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?

我最终围绕这种样式创建了一个包装器.唯一的好处是,如果您决定要从数据库中获取资源,则上面的代码无需更改,但是现在您必须添加资源条目,将其添加到接口中,然后实施以提取资源.它再次退出.我使用了nameof(),所以我不需要使用常量,但这仍然很脆弱,就好像属性名称或resx文件名更改一样,它将中断翻译而不会造成任何崩溃-我可能需要进行集成测试以确保我不会得到与发送的相同的值:

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];
}